@tangle-network/agent-provider-tangle 1.1.1 → 1.1.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  # @tangle-network/agent-provider-tangle
2
2
 
3
3
  Wraps `@tangle-network/sandbox` as an `AgentEnvironmentProvider`.
4
- The peer range is `>=0.34.6 <1.0.0`, and this package is developed and tested against 0.34.6.
4
+ The peer range is `>=0.34.6 <1.0.0`, and this package is developed and tested against 0.36.4.
5
5
  The floor is 0.34.6 because exact interactive attachment needs the host receiver, and workspace branching needs keyed snapshots, durable restores, inventory recovery, and cleanup.
6
6
  The provider fails closed when the configured backend or its catalog entry cannot be read.
7
7
  Newer SDKs may also provide `getBackend()` as a lookup over the same catalog.
@@ -205,6 +205,11 @@ cannot prove the complete operation surface.
205
205
  Recovery reads the account inventory through Sandbox offset pages of at most
206
206
  1,000 sandboxes and continues until a short page proves the inventory is
207
207
  complete.
208
+ If a live snapshot loses its marker tags, recovery reads the owner-scoped
209
+ operation record by key and binds its stored result to that live snapshot id.
210
+ The operation record alone never revives a deleted snapshot.
211
+ An older Sandbox deployment that cannot answer the owner-scoped lookup returns
212
+ `unknown` and does not create or mutate a checkpoint.
208
213
  The adapter also reads checkpoints from the previous marker format while writing only the current bounded format.
209
214
  Malformed, repeated, failed, or over-bound pages return `unknown` and do not
210
215
  mutate a resource.
@@ -241,6 +246,9 @@ if (checkpoint?.status === "created" || checkpoint?.status === "replayed") {
241
246
  ### Confidential forks
242
247
 
243
248
  Sandbox returns raw TEE evidence, not a verified claim.
249
+ The deployed Tangle job does not carry snapshot restore or sealed-persistence inputs.
250
+ The provider therefore refuses new confidential workspace forks before creation.
251
+ Existing confidential fork records still require a matching TEE report and an external verifier before they become trusted.
244
252
  Pass `confidentialAttestationVerifier` to `createTangleProvider` to connect a
245
253
  trusted provider-key and measurement verifier.
246
254
  The callback receives the raw report, the expected environment binding, and
@@ -86,6 +86,8 @@ export function defaultTangleSandboxCapabilities(harness) {
86
86
  branching: {
87
87
  checkpoint: true,
88
88
  fork: true,
89
+ // The deployed Tangle job does not carry snapshot restore inputs.
90
+ confidential: false,
89
91
  retrySafe: true,
90
92
  lookup: true,
91
93
  cleanup: true,
@@ -361,11 +363,12 @@ export function narrowedTangleCapabilities(declared, support, deployment, option
361
363
  // An incomplete Sandbox surface clears every branching flag together. A
362
364
  // partial claim would let a caller start an operation it cannot recover.
363
365
  branching: support.workspaceBranching
364
- ? { ...declaredBranching }
366
+ ? { ...declaredBranching, confidential: false }
365
367
  : {
366
368
  ...declaredBranching,
367
369
  checkpoint: false,
368
370
  fork: false,
371
+ confidential: false,
369
372
  ...(declaredBranching.retrySafe !== undefined ? { retrySafe: false } : {}),
370
373
  ...(declaredBranching.lookup !== undefined ? { lookup: false } : {}),
371
374
  ...(declaredBranching.cleanup !== undefined ? { cleanup: false } : {}),
@@ -16,6 +16,18 @@ const MAX_MARKER_TAG_LENGTH = 128;
16
16
  const MARKER_CHUNK_SIZE = 80;
17
17
  const LEGACY_MARKER_CHUNK_SIZE = 240;
18
18
  const MAX_MARKER_CHUNKS = 512;
19
+ const TANGLE_ATTESTATION_NONCE_PATTERN = /^(?:0x)?[0-9a-fA-F]{64}(?:[0-9a-fA-F]{64})?$/;
20
+ function confidentialForkRefusal(request) {
21
+ const confidential = request.confidential;
22
+ if (confidential?.requested !== true)
23
+ return undefined;
24
+ if (confidential.nonce === undefined ||
25
+ !TANGLE_ATTESTATION_NONCE_PATTERN.test(confidential.nonce))
26
+ return "Tangle confidential forks require a 32-64 byte hexadecimal attestation nonce";
27
+ if (confidential.sealed === true)
28
+ return "Tangle confidential forks cannot prove sealed no-persistence because the deployed job does not carry that requirement";
29
+ return "Tangle confidential workspace forks are unavailable because the deployed job does not carry snapshot restore inputs";
30
+ }
19
31
  /**
20
32
  * Carry the verifier only when the caller supplied one. Every option type that
21
33
  * accepts it is exact-optional, so an explicit `undefined` is not an absent key.
@@ -45,8 +57,8 @@ export function createTangleWorkspaceBranching(options) {
45
57
  *
46
58
  * The in-process record answers first; otherwise the Sandbox inventory and
47
59
  * operation ledger rebuild it after a restart. `absent` means the key has no
48
- * resource this provider created, which is the only state where a caller may
49
- * go on to create one.
60
+ * record, which is the only state where a caller may go on to create one.
61
+ * `retired` means the durable operation settled but its snapshot is gone.
50
62
  */
51
63
  const resolveCheckpoint = async (request, signal) => {
52
64
  const local = checkpoints.get(request.idempotencyKey);
@@ -58,30 +70,20 @@ export function createTangleWorkspaceBranching(options) {
58
70
  existingRequestDigest: local.request.requestDigest,
59
71
  };
60
72
  }
61
- const recovered = await findCheckpointByKey(box, request.idempotencyKey, signal);
62
- if (recovered === undefined) {
73
+ const recovered = await reconcileCheckpoint(box, provider, request, signal);
74
+ if (recovered.state === "undecided") {
63
75
  return {
64
76
  state: "undecided",
65
- message: "Sandbox checkpoint inventory is unavailable",
66
- };
67
- }
68
- if (!recovered)
69
- return { state: "absent" };
70
- if (recovered.marker.requestDigest !== request.requestDigest) {
71
- return {
72
- state: "conflict",
73
- existingRequestDigest: recovered.marker.requestDigest,
77
+ message: recovered.reason === "inventory_unavailable"
78
+ ? "Sandbox checkpoint inventory is unavailable"
79
+ : "Sandbox checkpoint metadata is invalid",
74
80
  };
75
81
  }
76
- const record = checkpointRecordFromSnapshot(recovered.marker.request, recovered.snapshot);
77
- if (!record) {
78
- return {
79
- state: "undecided",
80
- message: "Sandbox checkpoint metadata is invalid",
81
- };
82
+ if (recovered.state === "found") {
83
+ checkpoints.set(request.idempotencyKey, recovered.record);
84
+ return { state: "known", record: recovered.record };
82
85
  }
83
- checkpoints.set(request.idempotencyKey, record);
84
- return { state: "known", record };
86
+ return recovered;
85
87
  };
86
88
  /** The fork equivalent of {@link resolveCheckpoint}. */
87
89
  const resolveFork = async (request, signal) => {
@@ -141,17 +143,21 @@ export function createTangleWorkspaceBranching(options) {
141
143
  if (known.state === "undecided") {
142
144
  return checkpointUnknown(request, `${known.message}; retry after reconciliation`, true);
143
145
  }
146
+ if (known.state === "retired") {
147
+ return checkpointUnknown(request, "Sandbox checkpoint operation is settled but its snapshot is no longer present; the idempotency key cannot be reused", false);
148
+ }
144
149
  if (known.state === "known") {
145
150
  return checkpointSuccess(request, known.record.checkpoint, "replayed");
146
151
  }
147
152
  const tags = checkpointMarkerTags(request);
148
153
  let result;
149
154
  try {
155
+ operation?.signal?.throwIfAborted();
150
156
  result = await awaitWithSignal(box.snapshot?.({ tags, idempotencyKey: request.idempotencyKey }), operation?.signal);
151
157
  }
152
158
  catch (error) {
153
159
  operation?.signal?.throwIfAborted();
154
- const conflict = await checkpointConflictFromRemote(box, request, operation?.signal);
160
+ const conflict = await checkpointConflictFromRemote(box, provider, request, operation?.signal);
155
161
  return (conflict ??
156
162
  checkpointUnknown(request, `Sandbox checkpoint outcome is unresolved: ${safeError(error)}`, true));
157
163
  }
@@ -168,6 +174,26 @@ export function createTangleWorkspaceBranching(options) {
168
174
  if (resultMarker && resultMarker.requestDigest !== request.requestDigest) {
169
175
  return checkpointConflict(request, resultMarker.requestDigest);
170
176
  }
177
+ if (resultMarker &&
178
+ !checkpointMarkerBelongsToSource(resultMarker, provider, box.id)) {
179
+ return checkpointUnknown(request, "Sandbox checkpoint acknowledgement belongs to another source", false);
180
+ }
181
+ if (result.idempotency.outcome === "replayed") {
182
+ const recovered = await reconcileCheckpoint(box, provider, request, operation?.signal);
183
+ if (recovered.state === "retired" || recovered.state === "absent") {
184
+ return checkpointUnknown(request, "Sandbox replay acknowledged a checkpoint that is no longer present", false);
185
+ }
186
+ if (recovered.state === "undecided") {
187
+ return checkpointUnknown(request, recovered.reason === "metadata_invalid"
188
+ ? "Sandbox replay returned invalid checkpoint metadata"
189
+ : "Sandbox replay could not be reconciled with live checkpoint inventory", true);
190
+ }
191
+ if (recovered.state === "conflict") {
192
+ return checkpointConflict(request, recovered.existingRequestDigest);
193
+ }
194
+ checkpoints.set(request.idempotencyKey, recovered.record);
195
+ return checkpointSuccess(request, recovered.record.checkpoint, "replayed");
196
+ }
171
197
  if (!resultMarker) {
172
198
  return checkpointUnknown(request, "Sandbox checkpoint acknowledgement omitted its provider recovery marker", true);
173
199
  }
@@ -187,20 +213,13 @@ export function createTangleWorkspaceBranching(options) {
187
213
  if (known.state === "undecided") {
188
214
  return checkpointLookupUnknown(request, known.message, true);
189
215
  }
216
+ if (known.state === "retired") {
217
+ return checkpointNotFound(request);
218
+ }
190
219
  if (known.state === "known") {
191
220
  return checkpointFound(request, known.record.checkpoint);
192
221
  }
193
- try {
194
- const lookup = await awaitWithSignal(box.getSnapshotOperation?.(request.idempotencyKey, { tags: [] }), operation?.signal);
195
- const settled = lookupOutcomeFromSandbox(lookup, "checkpoint");
196
- return settled.absent
197
- ? checkpointNotFound(request)
198
- : checkpointLookupUnknown(request, settled.message, settled.retryable);
199
- }
200
- catch (error) {
201
- operation?.signal?.throwIfAborted();
202
- return checkpointLookupUnknown(request, `Sandbox checkpoint lookup failed: ${safeError(error)}`, true);
203
- }
222
+ return checkpointNotFound(request);
204
223
  };
205
224
  const deleteCheckpoint = async (input, operation) => {
206
225
  const request = WorkspaceCleanupRequestSchema.parse(input);
@@ -293,6 +312,10 @@ export function createTangleWorkspaceBranching(options) {
293
312
  if (known.state === "known") {
294
313
  return forkSuccess(request, known.record.environment, "replayed");
295
314
  }
315
+ const confidentialRefusal = confidentialForkRefusal(request);
316
+ if (confidentialRefusal !== undefined) {
317
+ return forkUnknown(request, confidentialRefusal, false);
318
+ }
296
319
  const checkpoint = [...checkpoints.values()].some((record) => canonicalCandidateDigest(record.checkpoint) ===
297
320
  canonicalCandidateDigest(request.checkpoint))
298
321
  ? true
@@ -624,6 +647,7 @@ async function confidentialAttestationForChild(request, child, provider, verifie
624
647
  const material = {
625
648
  provider,
626
649
  requested: true,
650
+ tee: response.attestation.tee_type,
627
651
  nonce: confidential.nonce,
628
652
  measurement,
629
653
  environmentId: child.id,
@@ -681,7 +705,7 @@ async function confidentialAttestationForChild(request, child, provider, verifie
681
705
  }
682
706
  function validTeeReport(report) {
683
707
  return (!!report &&
684
- safeString(report.tee_type) !== undefined &&
708
+ safeIdentifier(report.tee_type) !== undefined &&
685
709
  Array.isArray(report.evidence) &&
686
710
  report.evidence.length <= MAX_TEE_EVIDENCE_BYTES &&
687
711
  report.evidence.every((value) => Number.isInteger(value) && value >= 0 && value <= 255) &&
@@ -714,6 +738,11 @@ function validSnapshotOperationResult(value) {
714
738
  safeIdentifier(value.snapshotId) !== undefined &&
715
739
  validOperationDate(value.createdAt));
716
740
  }
741
+ function validTaggedSnapshotOperationResult(value) {
742
+ return (validSnapshotOperationResult(value) &&
743
+ Array.isArray(value.tags) &&
744
+ value.tags.every((tag) => safeString(tag) !== undefined));
745
+ }
717
746
  function validForkOperationChildResult(value) {
718
747
  return (validOperationRecord(value) &&
719
748
  safeIdentifier(value.sandboxId ?? value.id) !== undefined &&
@@ -779,6 +808,7 @@ function forkMarkerMetadata(request, materialization = "snapshot") {
779
808
  * until the ledger reports the operation succeeded.
780
809
  */
781
810
  async function checkpointOperationLookup(box, marker, signal) {
811
+ signal?.throwIfAborted();
782
812
  const lookup = await awaitWithSignal(box.getSnapshotOperation?.(marker.idempotencyKey, {
783
813
  tags: marker.legacy
784
814
  ? legacyCheckpointMarkerTags(marker.request)
@@ -786,6 +816,11 @@ async function checkpointOperationLookup(box, marker, signal) {
786
816
  }), signal);
787
817
  return lookup;
788
818
  }
819
+ /** Read the durable record by its owner-scoped key when no request body remains. */
820
+ async function checkpointOperationLookupByKey(box, idempotencyKey, signal) {
821
+ signal?.throwIfAborted();
822
+ return await awaitWithSignal(box.getSnapshotOperation?.(idempotencyKey), signal);
823
+ }
789
824
  async function checkpointOperationSucceeded(box, marker, signal) {
790
825
  const lookup = await checkpointOperationLookup(box, marker, signal);
791
826
  return (lookup?.outcome === "found" &&
@@ -833,9 +868,10 @@ function markerTags(kind, idempotencyKey, requestDigest, marker) {
833
868
  return tag;
834
869
  });
835
870
  }
836
- async function findCheckpointByKey(box, key, signal) {
871
+ async function findCheckpointByKey(box, provider, key, signal) {
837
872
  let snapshots;
838
873
  try {
874
+ signal?.throwIfAborted();
839
875
  const listed = await awaitWithSignal(box.listSnapshots?.(), signal);
840
876
  if (!Array.isArray(listed))
841
877
  return undefined;
@@ -848,13 +884,21 @@ async function findCheckpointByKey(box, key, signal) {
848
884
  if (!Array.isArray(snapshots) || snapshots.length > MAX_LIST_RESULTS) {
849
885
  return undefined;
850
886
  }
887
+ const snapshotIds = new Set();
888
+ let found;
851
889
  let unresolved = false;
852
890
  for (const snapshot of snapshots) {
853
891
  if (!validSnapshotInfo(snapshot, box.id))
854
892
  return undefined;
893
+ if (snapshotIds.has(snapshot.snapshotId))
894
+ return undefined;
895
+ snapshotIds.add(snapshot.snapshotId);
855
896
  const marker = checkpointMarkerFromTags(snapshot.tags, key);
856
897
  if (!marker)
857
898
  continue;
899
+ if (!checkpointMarkerBelongsToSource(marker, provider, box.id)) {
900
+ return undefined;
901
+ }
858
902
  try {
859
903
  const lookup = await checkpointOperationLookup(box, marker, signal);
860
904
  if (lookup?.outcome === "found" &&
@@ -863,7 +907,10 @@ async function findCheckpointByKey(box, key, signal) {
863
907
  const authoritative = snapshotFromOperationResult(snapshot, lookup);
864
908
  if (authoritative === undefined)
865
909
  return undefined;
866
- return { snapshot: authoritative, marker };
910
+ if (found !== undefined)
911
+ return undefined;
912
+ found = { state: "found", snapshot: authoritative, marker };
913
+ continue;
867
914
  }
868
915
  unresolved = true;
869
916
  }
@@ -872,7 +919,69 @@ async function findCheckpointByKey(box, key, signal) {
872
919
  return undefined;
873
920
  }
874
921
  }
875
- return unresolved ? undefined : null;
922
+ if (found !== undefined)
923
+ return found;
924
+ if (unresolved)
925
+ return undefined;
926
+ // Some storage backends retain the snapshot but omit caller tags from a
927
+ // later inventory read. The owner-scoped operation record retains the exact
928
+ // acknowledgement, including those tags. Bind that record to a currently
929
+ // live snapshot id before recovering it; neither record is sufficient alone.
930
+ let lookup;
931
+ try {
932
+ lookup = await checkpointOperationLookupByKey(box, key, signal);
933
+ }
934
+ catch {
935
+ signal?.throwIfAborted();
936
+ return undefined;
937
+ }
938
+ if (lookup?.outcome === "not_found" && lookup.kind === "checkpoint") {
939
+ return null;
940
+ }
941
+ if (lookup?.outcome !== "found" ||
942
+ lookup.kind !== "checkpoint" ||
943
+ lookup.state !== "succeeded") {
944
+ return undefined;
945
+ }
946
+ if (snapshots.length === 0)
947
+ return { state: "retired" };
948
+ if (!validTaggedSnapshotOperationResult(lookup.result))
949
+ return undefined;
950
+ const live = snapshots.filter((snapshot) => snapshot.snapshotId === lookup.result?.snapshotId);
951
+ if (live.length === 0)
952
+ return { state: "retired" };
953
+ if (live.length !== 1)
954
+ return undefined;
955
+ const marker = checkpointMarkerFromTags(lookup.result.tags, key);
956
+ if (!marker ||
957
+ !checkpointMarkerBelongsToSource(marker, provider, box.id)) {
958
+ return undefined;
959
+ }
960
+ const authoritative = snapshotFromOperationResult(live[0], lookup);
961
+ return authoritative === undefined
962
+ ? undefined
963
+ : { state: "found", snapshot: authoritative, marker };
964
+ }
965
+ /** Normalize one remote checkpoint recovery attempt for every caller. */
966
+ async function reconcileCheckpoint(box, provider, request, signal) {
967
+ const recovered = await findCheckpointByKey(box, provider, request.idempotencyKey, signal);
968
+ if (recovered === undefined) {
969
+ return { state: "undecided", reason: "inventory_unavailable" };
970
+ }
971
+ if (recovered === null)
972
+ return { state: "absent" };
973
+ if (recovered.state === "retired")
974
+ return recovered;
975
+ if (recovered.marker.requestDigest !== request.requestDigest) {
976
+ return {
977
+ state: "conflict",
978
+ existingRequestDigest: recovered.marker.requestDigest,
979
+ };
980
+ }
981
+ const record = checkpointRecordFromSnapshot(recovered.marker.request, recovered.snapshot);
982
+ return record === undefined
983
+ ? { state: "undecided", reason: "metadata_invalid" }
984
+ : { state: "found", record };
876
985
  }
877
986
  /**
878
987
  * Prefer the durable operation result over inventory metadata.
@@ -1160,6 +1269,10 @@ function markerBelongsToSource(marker, provider, sourceEnvironmentId) {
1160
1269
  return (marker.request.checkpoint.provider === provider &&
1161
1270
  marker.request.checkpoint.source.environmentId === sourceEnvironmentId);
1162
1271
  }
1272
+ function checkpointMarkerBelongsToSource(marker, provider, sourceEnvironmentId) {
1273
+ return (marker.request.source.provider === provider &&
1274
+ marker.request.source.environmentId === sourceEnvironmentId);
1275
+ }
1163
1276
  function checkpointMarkerFromTags(tags, key) {
1164
1277
  if (!Array.isArray(tags) ||
1165
1278
  tags.length > MAX_MARKER_CHUNKS + 3 ||
@@ -1307,19 +1420,19 @@ function forkMarkerFromMetadata(metadata, key) {
1307
1420
  * digest covers the Sandbox request body, not this interface's identity, so it
1308
1421
  * is never presented as an interface digest.
1309
1422
  */
1310
- async function checkpointConflictFromRemote(box, request, signal) {
1423
+ async function checkpointConflictFromRemote(box, provider, request, signal) {
1311
1424
  signal?.throwIfAborted();
1312
- const recovered = await findCheckpointByKey(box, request.idempotencyKey, signal);
1425
+ const recovered = await reconcileCheckpoint(box, provider, request, signal);
1313
1426
  signal?.throwIfAborted();
1314
- if (!recovered)
1315
- return undefined;
1316
- if (recovered.marker.requestDigest !== request.requestDigest) {
1317
- return checkpointConflict(request, recovered.marker.requestDigest);
1427
+ if (recovered.state === "retired") {
1428
+ return checkpointUnknown(request, "Sandbox checkpoint operation is settled but its snapshot is no longer present; the idempotency key cannot be reused", false);
1318
1429
  }
1319
- const record = checkpointRecordFromSnapshot(recovered.marker.request, recovered.snapshot);
1320
- return record === undefined
1430
+ if (recovered.state === "conflict") {
1431
+ return checkpointConflict(request, recovered.existingRequestDigest);
1432
+ }
1433
+ return recovered.state !== "found"
1321
1434
  ? undefined
1322
- : checkpointSuccess(request, record.checkpoint, "replayed");
1435
+ : checkpointSuccess(request, recovered.record.checkpoint, "replayed");
1323
1436
  }
1324
1437
  /** The fork equivalent of {@link checkpointConflictFromRemote}. */
1325
1438
  async function forkConflictFromRemote(client, box, provider, request, verifier, signal) {
@@ -1338,10 +1451,10 @@ async function forkConflictFromRemote(client, box, provider, request, verifier,
1338
1451
  : forkSuccess(request, environment, "replayed");
1339
1452
  }
1340
1453
  /**
1341
- * Read a ledger answer for a key that left no marked resource behind.
1454
+ * Read a fork ledger answer for a key that left no marked resource behind.
1342
1455
  *
1343
1456
  * `absent` is the settled answer: a decided operation with no inventory marker
1344
- * means the resource was cleaned after creation, and the provider must not
1457
+ * means the child was cleaned after creation, and the provider must not
1345
1458
  * resurrect it from the ledger. Every other state is undecided for the caller.
1346
1459
  */
1347
1460
  function lookupOutcomeFromSandbox(lookup, kind) {
package/package.json CHANGED
@@ -1,15 +1,16 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-provider-tangle",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "description": "AgentEnvironmentProvider adapter for Tangle sandboxes",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
- "main": "dist/index.js",
8
- "types": "src/index.ts",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
9
  "exports": {
10
10
  ".": {
11
11
  "import": "./dist/index.js",
12
- "types": "./src/index.ts"
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
13
14
  }
14
15
  },
15
16
  "repository": {
@@ -19,16 +20,7 @@
19
20
  },
20
21
  "publishConfig": {
21
22
  "access": "public",
22
- "registry": "https://registry.npmjs.org",
23
- "main": "./dist/index.js",
24
- "types": "./dist/index.d.ts",
25
- "exports": {
26
- ".": {
27
- "import": "./dist/index.js",
28
- "types": "./dist/index.d.ts",
29
- "default": "./dist/index.js"
30
- }
31
- }
23
+ "registry": "https://registry.npmjs.org"
32
24
  },
33
25
  "files": [
34
26
  "dist/index.d.ts",
@@ -98,15 +90,8 @@
98
90
  "README.md",
99
91
  "LICENSE"
100
92
  ],
101
- "scripts": {
102
- "build": "tsc -p tsconfig.json",
103
- "check-types": "tsc --noEmit",
104
- "clean": "rm -rf dist",
105
- "prepublishOnly": "pnpm run build",
106
- "test": "vitest run src"
107
- },
108
93
  "dependencies": {
109
- "@tangle-network/agent-interface": "^2.2.0"
94
+ "@tangle-network/agent-interface": "^2.3.0"
110
95
  },
111
96
  "peerDependencies": {
112
97
  "@tangle-network/sandbox": ">=0.34.6 <1.0.0"
@@ -117,12 +102,18 @@
117
102
  }
118
103
  },
119
104
  "devDependencies": {
120
- "@tangle-network/agent-eval": "0.170.0",
121
- "@tangle-network/agent-provider-testkit": "workspace:*",
122
- "@tangle-network/agent-runtime": "0.184.0",
123
- "@tangle-network/sandbox": "0.34.6",
124
- "@types/node": "catalog:",
105
+ "@tangle-network/agent-eval": "0.173.0",
106
+ "@tangle-network/agent-runtime": "0.190.0",
107
+ "@tangle-network/sandbox": "0.36.4",
108
+ "@types/node": "26.4.0",
125
109
  "typescript": "7.0.2",
126
- "vitest": "catalog:"
110
+ "vitest": "4.1.11",
111
+ "@tangle-network/agent-provider-testkit": "0.9.1"
112
+ },
113
+ "scripts": {
114
+ "build": "tsc -p tsconfig.json",
115
+ "check-types": "tsc --noEmit",
116
+ "clean": "rm -rf dist",
117
+ "test": "vitest run src"
127
118
  }
128
- }
119
+ }