@tangle-network/agent-runtime 0.94.12 → 0.95.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.
Files changed (80) hide show
  1. package/dist/agent.d.ts +2 -25
  2. package/dist/agent.js +9 -12
  3. package/dist/agent.js.map +1 -1
  4. package/dist/{agentic-generator-DDMM45kZ.d.ts → agentic-generator-hCaQRAes.d.ts} +0 -1
  5. package/dist/analyst-loop.d.ts +1 -1
  6. package/dist/candidate-execution/index.d.ts +43 -16
  7. package/dist/candidate-execution/index.js +18 -8
  8. package/dist/{chunk-VYA2YEKA.js → chunk-2KGAN2HM.js} +83 -14
  9. package/dist/chunk-2KGAN2HM.js.map +1 -0
  10. package/dist/{chunk-FTXM7Q7P.js → chunk-6YBA64Z2.js} +120 -724
  11. package/dist/chunk-6YBA64Z2.js.map +1 -0
  12. package/dist/{chunk-HGRW27YY.js → chunk-AP7CPGMZ.js} +139 -19
  13. package/dist/chunk-AP7CPGMZ.js.map +1 -0
  14. package/dist/{chunk-XP5KDM3R.js → chunk-BPGXIKK7.js} +3 -3
  15. package/dist/{chunk-D3H7F6L2.js → chunk-DHCHL6OG.js} +2 -3
  16. package/dist/chunk-DHCHL6OG.js.map +1 -0
  17. package/dist/{chunk-PCURO3DL.js → chunk-G55QE4IQ.js} +512 -36
  18. package/dist/chunk-G55QE4IQ.js.map +1 -0
  19. package/dist/{chunk-C3UKLQ54.js → chunk-IADLKE7I.js} +14 -4
  20. package/dist/chunk-IADLKE7I.js.map +1 -0
  21. package/dist/{chunk-WIPGQ4GT.js → chunk-IMSNJSXH.js} +1 -1
  22. package/dist/{chunk-WIPGQ4GT.js.map → chunk-IMSNJSXH.js.map} +1 -1
  23. package/dist/{chunk-VSWBYWFK.js → chunk-M6MD6JBS.js} +20 -27
  24. package/dist/chunk-M6MD6JBS.js.map +1 -0
  25. package/dist/chunk-MKGRLDWB.js +684 -0
  26. package/dist/chunk-MKGRLDWB.js.map +1 -0
  27. package/dist/{chunk-AEG3NGJ2.js → chunk-Q2JSAVQ3.js} +34 -2
  28. package/dist/chunk-Q2JSAVQ3.js.map +1 -0
  29. package/dist/{chunk-37V4KQDC.js → chunk-WTZ37EQY.js} +1021 -514
  30. package/dist/chunk-WTZ37EQY.js.map +1 -0
  31. package/dist/{chunk-U33YZ7B2.js → chunk-YLUOTX6U.js} +4 -4
  32. package/dist/{chunk-33OG2NN3.js → chunk-Z5I642SY.js} +2 -2
  33. package/dist/{completion-gate-D1gX1-hg.d.ts → completion-gate-C80jiRfN.d.ts} +1 -1
  34. package/dist/conversation.d.ts +12 -1
  35. package/dist/conversation.js +2 -2
  36. package/dist/{coordination-Dr_axlAf.d.ts → coordination-BFE3Den7.d.ts} +10 -11
  37. package/dist/environment-provider.d.ts +2 -2
  38. package/dist/environment-provider.js +1 -1
  39. package/dist/{improve-BN3HyXIO.d.ts → improve-B-UYaEH5.d.ts} +3 -3
  40. package/dist/index.d.ts +22 -25
  41. package/dist/index.js +45 -50
  42. package/dist/index.js.map +1 -1
  43. package/dist/intelligence.d.ts +177 -119
  44. package/dist/intelligence.js +501 -342
  45. package/dist/intelligence.js.map +1 -1
  46. package/dist/knowledge.d.ts +22 -11
  47. package/dist/knowledge.js +9 -4
  48. package/dist/lifecycle.d.ts +2 -2
  49. package/dist/lifecycle.js +1 -1
  50. package/dist/{loop-runner-bin-BRQSQdHa.d.ts → loop-runner-bin-BIQldFS8.d.ts} +2 -2
  51. package/dist/loop-runner-bin.d.ts +5 -5
  52. package/dist/loop-runner-bin.js +6 -6
  53. package/dist/loops.d.ts +12 -12
  54. package/dist/loops.js +5 -5
  55. package/dist/mcp/bin.js +3 -3
  56. package/dist/mcp/index.d.ts +6 -6
  57. package/dist/mcp/index.js +6 -6
  58. package/dist/{mcp-serve-verifier-DQQDbuyz.d.ts → mcp-serve-verifier-Bs_n0xPc.d.ts} +1 -1
  59. package/dist/primeintellect/index.js +1 -1
  60. package/dist/profile-DbfaMTdk.d.ts +233 -0
  61. package/dist/profiles.d.ts +1 -1
  62. package/dist/{supervise-DmYOug5f.d.ts → supervise-BLPI50-w.d.ts} +3 -3
  63. package/dist/{types-ByAYqlVb.d.ts → types-B3vAW0Oq.d.ts} +1 -1
  64. package/dist/types-CWqfCO8s.d.ts +491 -0
  65. package/dist/{types-1d5QGK3t.d.ts → types-CmnA2iL3.d.ts} +3 -3
  66. package/dist/{worktree-fanout-CPprU-qI.d.ts → worktree-fanout-DCA3G4bO.d.ts} +3 -3
  67. package/package.json +13 -10
  68. package/dist/chunk-37V4KQDC.js.map +0 -1
  69. package/dist/chunk-AEG3NGJ2.js.map +0 -1
  70. package/dist/chunk-C3UKLQ54.js.map +0 -1
  71. package/dist/chunk-D3H7F6L2.js.map +0 -1
  72. package/dist/chunk-FTXM7Q7P.js.map +0 -1
  73. package/dist/chunk-HGRW27YY.js.map +0 -1
  74. package/dist/chunk-PCURO3DL.js.map +0 -1
  75. package/dist/chunk-VSWBYWFK.js.map +0 -1
  76. package/dist/chunk-VYA2YEKA.js.map +0 -1
  77. package/dist/prepare-DIeVgQG3.d.ts +0 -718
  78. /package/dist/{chunk-XP5KDM3R.js.map → chunk-BPGXIKK7.js.map} +0 -0
  79. /package/dist/{chunk-U33YZ7B2.js.map → chunk-YLUOTX6U.js.map} +0 -0
  80. /package/dist/{chunk-33OG2NN3.js.map → chunk-Z5I642SY.js.map} +0 -0
@@ -1,12 +1,7 @@
1
1
  import {
2
- CLAIM_FORMAT_VERSION,
3
- PENDING_FORMAT_VERSION,
4
- PHASE_FORMAT_VERSION,
5
2
  RecoveryAgentCandidateTraceStore,
6
- TERMINAL_FORMAT_VERSION,
7
3
  applyExactAgentProfileDiff,
8
4
  assertExactObjectKeys,
9
- assertNoGitIndirection,
10
5
  assertPreparedCandidateIntegrity,
11
6
  assertRecoveryMatchesStaged,
12
7
  assertTerminalAllowedInPhase,
@@ -18,68 +13,63 @@ import {
18
13
  candidateCleanupTimeout,
19
14
  canonicalCandidateBytes,
20
15
  canonicalCandidateDigest,
21
- captureMaterializedWorkspace,
22
16
  consumePreparedCandidateExecution,
23
- deepFreezeCandidate,
24
17
  embeddedCandidateArtifact,
25
18
  freezeGenericAgentCandidateProfile,
26
19
  immutableCandidateValue,
20
+ omitTopLevelDigest,
27
21
  parseExactAgentProfile,
28
22
  parseExactAgentProfileDiff,
29
23
  parseExactCandidateProfile,
30
24
  persistCandidateModelSettlementEvidence,
31
25
  persistCandidateOutputArtifact,
32
- readCandidateGitTreeFiles,
26
+ readVerifiedArtifact,
33
27
  recoveredTerminalRecord,
34
28
  rejectedFinish,
35
29
  rejectedStage,
36
30
  requireStagedTerminal,
37
- runCandidateGit,
38
- sealAgentCandidateBundle,
39
- sealAgentCandidateExecutorFinalCapture,
31
+ sealAgentCandidateExecutorStopAcknowledgement,
40
32
  sealAgentCandidateModelSettlement,
41
33
  sealTerminalDigest,
42
- sha256Bytes,
43
34
  terminalRecord,
44
35
  usdToNanos,
45
- verifyBytes,
46
- verifyMaterializedWorkspace,
47
36
  withinCandidateCleanupDeadline
48
- } from "./chunk-37V4KQDC.js";
37
+ } from "./chunk-WTZ37EQY.js";
49
38
 
50
39
  // src/candidate-execution/builder.ts
51
40
  import { verifyCodeSurface } from "@tangle-network/agent-eval/campaign";
52
- function buildAgentCandidateBundle(input) {
53
- if (Object.hasOwn(input.lineage, "profileDiffIds")) {
54
- throw new Error("profileDiffIds is derived from the profile source and cannot be supplied");
41
+
42
+ // src/candidate-execution/bundle.ts
43
+ import { agentCandidateBundleSchema } from "@tangle-network/agent-interface";
44
+ function sealAgentCandidateBundle(input) {
45
+ const digest = canonicalCandidateDigest(input);
46
+ const parsed = agentCandidateBundleSchema.parse({ ...input, digest });
47
+ const parsedDigest = canonicalCandidateDigest(omitTopLevelDigest(parsed));
48
+ if (parsedDigest !== digest) {
49
+ throw new Error("candidate bundle changed while validating its canonical wire shape");
55
50
  }
56
- const compiledProfile = compileCandidateProfile(input.profile);
57
- const profileDiffIds = compiledProfile.profileDiffIds;
51
+ return immutableCandidateValue(parsed);
52
+ }
53
+
54
+ // src/candidate-execution/builder.ts
55
+ function buildAgentCandidateBundle(input) {
58
56
  const bundle = {
59
- schemaVersion: 1,
60
57
  kind: "agent-candidate-bundle",
61
58
  digestAlgorithm: "rfc8785-sha256",
62
- profile: compiledProfile.profile,
59
+ profile: compileCandidateProfile(input.profile),
63
60
  code: compileCandidateCode(input.code),
64
61
  execution: input.execution,
65
- ...input.knowledge ? { knowledge: input.knowledge } : {},
66
- memory: input.memory,
67
- lineage: {
68
- ...input.lineage,
69
- ...profileDiffIds.length > 0 ? { profileDiffIds } : {}
70
- }
62
+ ...input.knowledge !== void 0 ? { knowledge: input.knowledge } : {},
63
+ memory: input.memory
71
64
  };
72
65
  return sealAgentCandidateBundle(bundle);
73
66
  }
74
67
  function compileCandidateProfile(source) {
75
68
  if (source.kind === "candidate-profile") {
76
- return {
77
- profile: parseExactCandidateProfile(source.profile),
78
- profileDiffIds: []
79
- };
69
+ return parseExactCandidateProfile(source.profile);
80
70
  }
81
71
  if (source.kind === "profile") {
82
- return { profile: freezeGenericAgentCandidateProfile(source.profile), profileDiffIds: [] };
72
+ return freezeGenericAgentCandidateProfile(source.profile);
83
73
  }
84
74
  if (source.kind !== "profile-diffs") {
85
75
  throw new Error(
@@ -90,13 +80,11 @@ function compileCandidateProfile(source) {
90
80
  throw new Error("profile-diffs source requires at least one AgentProfileDiff");
91
81
  }
92
82
  let profile = parseExactAgentProfile(source.base, "base profile");
93
- const profileDiffIds = [];
94
83
  for (const [index, inputDiff] of source.diffs.entries()) {
95
84
  const diff = parseExactAgentProfileDiff(inputDiff, `profile diff ${index}`);
96
85
  profile = applyExactAgentProfileDiff(profile, diff, `profile diff ${index}`);
97
- profileDiffIds.push(canonicalCandidateDigest(diff));
98
86
  }
99
- return { profile: freezeGenericAgentCandidateProfile(profile), profileDiffIds };
87
+ return freezeGenericAgentCandidateProfile(profile);
100
88
  }
101
89
  function compileCandidateCode(source) {
102
90
  if (source.kind === "disabled" || source.kind === "no-op") return source;
@@ -170,7 +158,6 @@ var FileAgentCandidateExecutionClaimStore = class {
170
158
  if (retryFailure) return rejectedRetry(claim, retryFailure);
171
159
  const lease = newLease(claim);
172
160
  const acquired = await writeRecordIfAbsent(this.directory, claimPath, {
173
- version: CLAIM_FORMAT_VERSION,
174
161
  ...claim,
175
162
  phase: "claimed",
176
163
  leaseDigest: leaseDigest(lease)
@@ -201,7 +188,6 @@ var FileAgentCandidateExecutionClaimStore = class {
201
188
  this.directory,
202
189
  this.transitionPath(stored.claim, 1),
203
190
  {
204
- version: PHASE_FORMAT_VERSION,
205
191
  kind: "candidate-execution-phase",
206
192
  executionId: stored.claim.executionId,
207
193
  attempt: stored.claim.attempt,
@@ -233,7 +219,6 @@ var FileAgentCandidateExecutionClaimStore = class {
233
219
  this.directory,
234
220
  this.transitionPath(stored.claim, transition),
235
221
  {
236
- version: PENDING_FORMAT_VERSION,
237
222
  kind: "candidate-execution-pending-terminal",
238
223
  terminal
239
224
  },
@@ -267,7 +252,6 @@ var FileAgentCandidateExecutionClaimStore = class {
267
252
  this.directory,
268
253
  terminalPath,
269
254
  {
270
- version: TERMINAL_FORMAT_VERSION,
271
255
  terminal: staged
272
256
  },
273
257
  this.ownerPublication(stored.claim)
@@ -295,7 +279,6 @@ var FileAgentCandidateExecutionClaimStore = class {
295
279
  this.directory,
296
280
  this.transitionPath(record2.claim, transition),
297
281
  {
298
- version: PENDING_FORMAT_VERSION,
299
282
  kind: "candidate-execution-pending-terminal",
300
283
  terminal: recovered
301
284
  }
@@ -315,7 +298,6 @@ var FileAgentCandidateExecutionClaimStore = class {
315
298
  }
316
299
  const terminalPath = this.terminalPath(attempt);
317
300
  const didFinish = await writeRecordIfAbsent(this.directory, terminalPath, {
318
- version: TERMINAL_FORMAT_VERSION,
319
301
  terminal: staged
320
302
  });
321
303
  if (didFinish) return Object.freeze({ finished: true, terminal: staged });
@@ -604,7 +586,7 @@ function createProtectedAgentCandidateModelPort(options) {
604
586
  grantDigest: request.grantDigest,
605
587
  model: request.resolved.model
606
588
  });
607
- if (state) assertWithinReservedLimits(sealed.fixedUsage, state);
589
+ if (state) assertWithinReservedLimits(sealed.usage, state);
608
590
  const settlementDigest = canonicalCandidateDigest(sealed.value);
609
591
  if (remembered?.settlementDigest && remembered.settlementDigest !== settlementDigest) {
610
592
  throw new Error("protected model settlement retry returned a different final ledger");
@@ -818,6 +800,10 @@ function record(value, label) {
818
800
  }
819
801
 
820
802
  // src/candidate-execution/recover.ts
803
+ import {
804
+ agentCandidateExecutionPlanMaterialSchema,
805
+ agentCandidateMaterializationReceiptSchema
806
+ } from "@tangle-network/agent-interface";
821
807
  async function recoverExpiredAgentCandidateExecution(options) {
822
808
  const record2 = await options.claimStore.getAttempt(options.attempt);
823
809
  if (!record2) throw new Error("candidate execution recovery attempt is missing");
@@ -835,12 +821,15 @@ async function recoverExpiredAgentCandidateExecution(options) {
835
821
  throw new Error("candidate execution lease has not expired");
836
822
  }
837
823
  const cleanupDeadlineAtMs = candidateCleanupDeadline(cleanupTimeoutMs);
838
- const controller = new AbortController();
839
- controller.abort(new Error("recovering an expired candidate execution"));
840
824
  const recoveryTraceStore = new RecoveryAgentCandidateTraceStore(options.traceStore);
825
+ const preparation = withinCandidateCleanupDeadline(
826
+ () => readRecoveryPreparation(record2.claim, options.outputArtifacts),
827
+ cleanupDeadlineAtMs,
828
+ "expired candidate preparation evidence read"
829
+ );
841
830
  const processClosure = withinCandidateCleanupDeadline(
842
- async () => {
843
- const stopped = await options.executor.stopAndCapture(
831
+ async (cleanupSignal) => {
832
+ const stopped = await options.executor.stop(
844
833
  {
845
834
  executionId: record2.claim.executionId,
846
835
  executionPlanDigest: record2.claim.executionPlanDigest
@@ -848,11 +837,23 @@ async function recoverExpiredAgentCandidateExecution(options) {
848
837
  {
849
838
  traceStore: recoveryTraceStore,
850
839
  reason: "failed",
851
- signal: controller.signal,
852
- deadlineAtMs: record2.claim.leaseExpiresAtMs
840
+ signal: cleanupSignal,
841
+ deadlineAtMs: cleanupDeadlineAtMs
853
842
  }
854
843
  );
855
- sealAgentCandidateExecutorFinalCapture(stopped);
844
+ sealAgentCandidateExecutorStopAcknowledgement(stopped);
845
+ if (options.executor.dispose) {
846
+ const disposed = await options.executor.dispose(
847
+ {
848
+ executionId: record2.claim.executionId,
849
+ executionPlanDigest: record2.claim.executionPlanDigest
850
+ },
851
+ { signal: cleanupSignal }
852
+ );
853
+ if (disposed.disposed !== true || Object.keys(disposed).some((key) => key !== "disposed")) {
854
+ throw new Error("expired candidate executor did not acknowledge resource disposal");
855
+ }
856
+ }
856
857
  return { stopped: true };
857
858
  },
858
859
  cleanupDeadlineAtMs,
@@ -895,13 +896,26 @@ async function recoverExpiredAgentCandidateExecution(options) {
895
896
  cleanupDeadlineAtMs,
896
897
  "expired candidate memory closure"
897
898
  ) : void 0;
898
- const operations = [processClosure, modelClosure, ...memoryClosure ? [memoryClosure] : []];
899
- const outcomes = await Promise.allSettled(operations);
900
- const failures = outcomes.filter((outcome) => outcome.status === "rejected").map((outcome) => outcome.reason);
899
+ const closureOutcomes = Promise.allSettled([
900
+ modelClosure,
901
+ memoryClosure ?? Promise.resolve(void 0)
902
+ ]);
903
+ const [preparationOutcome, processOutcome] = await Promise.allSettled([
904
+ preparation,
905
+ processClosure
906
+ ]);
907
+ const failures = [preparationOutcome, processOutcome].filter((outcome) => outcome.status === "rejected").map((outcome) => outcome.reason);
908
+ const [modelOutcome, memoryOutcome] = await closureOutcomes;
909
+ for (const outcome of [modelOutcome, memoryOutcome]) {
910
+ if (outcome.status === "rejected") failures.push(outcome.reason);
911
+ }
901
912
  if (failures.length > 0) {
902
913
  throw new AggregateError(failures, "expired candidate cleanup could not be proven");
903
914
  }
904
- const model = await modelClosure;
915
+ if (modelOutcome.status !== "fulfilled") {
916
+ throw new Error("expired candidate model settlement was not recovered");
917
+ }
918
+ const model = modelOutcome.value;
905
919
  const modelSettlement = await withinCandidateCleanupDeadline(
906
920
  () => persistCandidateModelSettlementEvidence(
907
921
  {
@@ -915,11 +929,33 @@ async function recoverExpiredAgentCandidateExecution(options) {
915
929
  cleanupDeadlineAtMs,
916
930
  "expired candidate model-settlement persistence"
917
931
  );
932
+ const failureClass = record2.phase === "claimed" && model.usage.modelCalls === 0 ? "pre-model-infrastructure" : "unknown";
933
+ const failureEvidence = await withinCandidateCleanupDeadline(
934
+ () => persistCandidateOutputArtifact(options.outputArtifacts, {
935
+ executionId: record2.claim.executionId,
936
+ purpose: "failure-evidence",
937
+ bytes: canonicalCandidateBytes({
938
+ kind: "agent-candidate-recovery-failure",
939
+ executionId: record2.claim.executionId,
940
+ attempt: record2.claim.attempt,
941
+ bundleDigest: record2.claim.bundleDigest,
942
+ executionPlanDigest: record2.claim.executionPlanDigest,
943
+ materializationReceiptDigest: record2.claim.preparationEvidence.materializationReceipt.sha256,
944
+ failureClass,
945
+ processStopped: true,
946
+ modelSettlementDigest: modelSettlement.digest,
947
+ memoryClosed: record2.claim.cleanup.memory !== void 0
948
+ })
949
+ }),
950
+ cleanupDeadlineAtMs,
951
+ "expired candidate failure-evidence persistence"
952
+ );
918
953
  const memory = record2.claim.cleanup.memory;
919
954
  return await options.claimStore.recoverExpired(options.attempt, {
920
- failureClass: record2.phase === "claimed" && model.fixedUsage.modelCalls === 0 ? "pre-model-infrastructure" : "unknown",
921
- usage: model.fixedUsage,
955
+ failureClass,
956
+ usage: model.usage,
922
957
  modelSettlement: modelSettlement.artifact,
958
+ failureEvidence,
923
959
  process: {
924
960
  stopped: true,
925
961
  executionPlanDigest: record2.claim.executionPlanDigest
@@ -939,689 +975,49 @@ async function recoverExpiredAgentCandidateExecution(options) {
939
975
  } : {}
940
976
  });
941
977
  }
942
-
943
- // src/candidate-execution/workspace-archive.ts
944
- import { randomUUID as randomUUID2 } from "crypto";
945
- import { constants as fsConstants } from "fs";
946
- import {
947
- lstat,
948
- mkdir as mkdir2,
949
- mkdtemp,
950
- open as open2,
951
- readdir,
952
- readFile,
953
- realpath,
954
- rename,
955
- rm,
956
- writeFile
957
- } from "fs/promises";
958
- import { tmpdir } from "os";
959
- import { dirname, isAbsolute, join as join2, resolve, sep, win32 } from "path";
960
- import { isAnyArrayBuffer, isSharedArrayBuffer } from "util/types";
961
- import { extract, pack } from "tar-stream";
962
- var archiveKind = "agent-candidate-workspace-archive";
963
- var workspaceEntryPrefix = "workspace/";
964
- var repositoryMetadataEntry = "metadata/repository.json";
965
- var repositoryBundleEntry = "metadata/repository.bundle";
966
- var fixedTarTime = /* @__PURE__ */ new Date(0);
967
- var defaultLimits = Object.freeze({
968
- maxArchiveBytes: 512 * 1024 * 1024,
969
- maxEmbeddedArtifactBytes: 1024 * 1024,
970
- maxFiles: 5e4,
971
- maxFileBytes: 128 * 1024 * 1024,
972
- maxTotalFileBytes: 256 * 1024 * 1024,
973
- maxPathBytes: 4096,
974
- maxRepositoryBundleBytes: 128 * 1024 * 1024
975
- });
976
- async function captureAgentCandidateWorkspace(rootInput, options = {}) {
977
- const root = resolve(rootInput);
978
- const limits = workspaceLimits(options.limits);
979
- const captured = options.includeRepository ? await captureRepository(root, limits) : await captureMaterializedWorkspace(root, {
980
- limits: {
981
- maxFiles: limits.maxFiles,
982
- maxFileBytes: limits.maxFileBytes,
983
- maxTotalFileBytes: limits.maxTotalFileBytes
984
- }
985
- });
986
- assertWorkspaceFilesWithinLimits(captured.files, limits);
987
- const repository = "repository" in captured ? captured.repository : void 0;
988
- return captureWorkspaceFiles(captured.files, repository, limits, options.artifactPersistence);
989
- }
990
- async function captureAgentCandidateWorkspaceFiles(input, options = {}) {
991
- const limits = workspaceLimits(options.limits);
992
- return captureWorkspaceFiles(
993
- normalizeWorkspaceFiles(input, limits),
994
- void 0,
995
- limits,
996
- options.artifactPersistence
978
+ async function readRecoveryPreparation(claim, artifacts) {
979
+ const [planBytes, receiptBytes] = await Promise.all([
980
+ readVerifiedArtifact(claim.preparationEvidence.executionPlan, artifacts),
981
+ readVerifiedArtifact(claim.preparationEvidence.materializationReceipt, artifacts)
982
+ ]);
983
+ const plan = agentCandidateExecutionPlanMaterialSchema.parse(
984
+ parseCanonicalBytes(planBytes, "plan")
997
985
  );
998
- }
999
- async function captureWorkspaceFiles(files, repository, limits, artifactPersistence) {
1000
- const material = workspaceManifest(files);
1001
- const manifest = canonicalCandidateBytes(material);
1002
- const archive = await encodeWorkspaceArchive(files, repository, limits.maxArchiveBytes);
1003
- if (!artifactPersistence && (manifest.byteLength > limits.maxEmbeddedArtifactBytes || archive.byteLength > limits.maxEmbeddedArtifactBytes)) {
1004
- throw new Error(
1005
- "candidate workspace artifacts exceed maxEmbeddedArtifactBytes; artifactPersistence is required"
1006
- );
986
+ if (claim.preparationEvidence.executionPlan.sha256 !== claim.executionPlanDigest) {
987
+ throw new Error("recovery execution plan artifact does not match the claimed digest");
1007
988
  }
1008
- const manifestArtifact = await captureWorkspaceArtifact("manifest", manifest, artifactPersistence);
1009
- const archiveArtifact = await captureWorkspaceArtifact("archive", archive, artifactPersistence);
1010
- const snapshot = deepFreezeCandidate({
1011
- schemaVersion: 1,
1012
- kind: "agent-candidate-workspace-snapshot",
1013
- digest: canonicalCandidateDigest(material),
1014
- material,
1015
- manifest: manifestArtifact,
1016
- archive: archiveArtifact
1017
- });
1018
- return Object.freeze({ snapshot, archive });
1019
- }
1020
- async function captureWorkspaceArtifact(kind, bytes, persistence) {
1021
- if (!persistence) return embeddedCandidateArtifact(bytes);
1022
- if (!persistence.executionId.trim()) {
1023
- throw new Error("candidate workspace artifact persistence requires an executionId");
1024
- }
1025
- return persistCandidateOutputArtifact(persistence.outputArtifacts, {
1026
- executionId: persistence.executionId,
1027
- purpose: kind === "manifest" ? "candidate-workspace-manifest" : "candidate-workspace-archive",
1028
- bytes,
1029
- ...persistence.signal ? { signal: persistence.signal } : {}
989
+ const receiptMaterial = parseCanonicalBytes(receiptBytes, "materialization receipt");
990
+ const receipt = agentCandidateMaterializationReceiptSchema.parse({
991
+ ...receiptMaterial,
992
+ digest: claim.preparationEvidence.materializationReceipt.sha256
1030
993
  });
1031
- }
1032
- function createAgentCandidateWorkspacePort(options = {}) {
1033
- const limits = workspaceLimits(options.limits);
1034
- const port = {
1035
- materialize: async ({ role, snapshot, archive, destination }) => {
1036
- await materializeAgentCandidateWorkspace({
1037
- role,
1038
- snapshot,
1039
- archive,
1040
- destination,
1041
- limits
1042
- });
1043
- }
1044
- };
1045
- return Object.freeze(port);
1046
- }
1047
- async function materializeAgentCandidateWorkspace(input) {
1048
- verifyBytes(
1049
- input.archive,
1050
- input.snapshot.archive.sha256,
1051
- input.snapshot.archive.byteLength,
1052
- "candidate workspace archive"
1053
- );
1054
- const decoded = await parseWorkspaceArchive(input.archive, input.limits);
1055
- if (decoded.repository && input.role !== "task") {
1056
- throw new Error("only task workspaces may carry a Git repository");
1057
- }
1058
- assertArchiveMatchesSnapshot(decoded.files, input.snapshot);
1059
- const destination = resolve(input.destination);
1060
- await prepareEmptyDestination(destination);
1061
- const staging = await mkdtemp(`${destination}.materializing-`);
1062
- try {
1063
- await writeWorkspaceFiles(staging, decoded.files);
1064
- if (decoded.repository) {
1065
- await materializeRepository(decoded.repository, staging);
1066
- }
1067
- await verifyMaterializedWorkspace(staging, input.snapshot.material, {
1068
- ignoredProtectedRootEntries: decoded.repository ? [".git"] : []
1069
- });
1070
- await rename(staging, destination);
1071
- } catch (error) {
1072
- await rm(staging, { recursive: true, force: true });
1073
- await rm(destination, { recursive: true, force: true });
1074
- throw error;
994
+ if (receipt.bundleDigest !== claim.bundleDigest || receipt.executionPlan.digest !== claim.executionPlanDigest || plan.runCell.bundleDigest !== claim.bundleDigest || plan.executionId !== claim.executionId) {
995
+ throw new Error("recovery preparation evidence does not match the durable claim");
1075
996
  }
997
+ return { plan, receipt };
1076
998
  }
1077
- async function captureRepository(root, limits) {
1078
- const stats = await lstat(root);
1079
- if (!stats.isDirectory() || stats.isSymbolicLink() || await realpath(root) !== root) {
1080
- throw new Error("candidate repository root must be a real directory");
1081
- }
1082
- const topLevel = (await runCandidateGit(root, ["rev-parse", "--show-toplevel"])).stdout.toString("utf8").trim();
1083
- if (resolve(topLevel) !== root) {
1084
- throw new Error("candidate repository capture must start at the Git worktree root");
1085
- }
1086
- const gitDir = resolve(
1087
- (await runCandidateGit(root, ["rev-parse", "--absolute-git-dir"])).stdout.toString("utf8").trim()
1088
- );
1089
- if (await realpath(gitDir) !== gitDir || gitDir.includes(":")) {
1090
- throw new Error("candidate repository Git object store has an unsupported path");
1091
- }
1092
- await assertNoGitIndirection(root, gitDir, "candidate repository");
1093
- const headCommit = (await runCandidateGit(root, ["rev-parse", "HEAD"])).stdout.toString("utf8").trim();
1094
- const headTree = (await runCandidateGit(root, ["rev-parse", "HEAD^{tree}"])).stdout.toString("utf8").trim();
1095
- assertGitObjectId(headCommit, "HEAD");
1096
- assertGitObjectId(headTree, "HEAD tree");
1097
- if (headCommit.length !== headTree.length) {
1098
- throw new Error("candidate repository HEAD and tree use different object formats");
1099
- }
1100
- const files = await readCandidateGitTreeFiles(root, headTree, {}, limits);
1101
- assertWorkspaceFilesWithinLimits(files, limits);
1102
- const reachableBytesText = (await runCandidateGit(root, ["rev-list", "--disk-usage", "--objects", "HEAD"])).stdout.toString("utf8").trim();
1103
- const reachableBytes = Number(reachableBytesText);
1104
- if (!Number.isSafeInteger(reachableBytes) || reachableBytes < 0) {
1105
- throw new Error("candidate repository reachable size is invalid");
1106
- }
1107
- if (reachableBytes > limits.maxRepositoryBundleBytes) {
1108
- throw new Error("candidate repository exceeds maxRepositoryBundleBytes");
1109
- }
1110
- const temporary = await mkdtemp(join2(tmpdir(), "agent-candidate-workspace-bundle-"));
1111
- const bundlePath = join2(temporary, `${randomUUID2()}.bundle`);
999
+ function parseCanonicalBytes(bytes, label) {
1000
+ let parsed;
1112
1001
  try {
1113
- await runCandidateGit(root, ["bundle", "create", bundlePath, "HEAD"]);
1114
- const bundle = Uint8Array.from(await readFile(bundlePath));
1115
- if (bundle.byteLength > limits.maxRepositoryBundleBytes) {
1116
- throw new Error("candidate repository bundle exceeds maxRepositoryBundleBytes");
1117
- }
1118
- return {
1119
- files,
1120
- repository: {
1121
- headCommit,
1122
- headTree,
1123
- bundle: {
1124
- sha256: sha256Bytes(bundle),
1125
- byteLength: bundle.byteLength,
1126
- bytes: bundle
1127
- }
1128
- }
1129
- };
1130
- } finally {
1131
- await rm(temporary, { recursive: true, force: true });
1132
- }
1133
- }
1134
- async function encodeWorkspaceArchive(files, repository, maxArchiveBytes) {
1135
- const archive = pack();
1136
- const output = collectStream(archive, maxArchiveBytes, "candidate workspace archive");
1137
- try {
1138
- await writeWorkspaceArchiveEntries(archive, files, repository);
1139
- archive.finalize();
1140
- return await output;
1002
+ parsed = JSON.parse(Buffer.from(bytes).toString("utf8"));
1141
1003
  } catch (error) {
1142
- archive.destroy(error instanceof Error ? error : new Error(String(error)));
1143
- await output.catch(() => void 0);
1144
- throw error;
1004
+ throw new Error(`candidate recovery ${label} is not JSON`, { cause: error });
1145
1005
  }
1146
- }
1147
- async function verifyCanonicalWorkspaceArchive(files, repository, expected, maxArchiveBytes) {
1148
- const archive = pack();
1149
- const comparison = streamEqualsBytes(
1150
- archive,
1151
- expected,
1152
- maxArchiveBytes,
1153
- "candidate workspace archive"
1154
- );
1155
- try {
1156
- await writeWorkspaceArchiveEntries(archive, files, repository);
1157
- archive.finalize();
1158
- if (!await comparison) throw new Error("candidate workspace tar is not canonical");
1159
- } catch (error) {
1160
- archive.destroy(error instanceof Error ? error : new Error(String(error)));
1161
- await comparison.catch(() => void 0);
1162
- throw error;
1006
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
1007
+ throw new Error(`candidate recovery ${label} must be an object`);
1163
1008
  }
1164
- }
1165
- async function writeWorkspaceArchiveEntries(archive, files, repository) {
1166
- for (const file of files) {
1167
- await writeTarEntry(archive, `${workspaceEntryPrefix}${file.path}`, file.mode, file.bytes);
1168
- }
1169
- if (!repository) return;
1170
- const metadata = canonicalCandidateBytes({
1171
- schemaVersion: 1,
1172
- kind: archiveKind,
1173
- headCommit: repository.headCommit,
1174
- headTree: repository.headTree,
1175
- bundle: {
1176
- sha256: repository.bundle.sha256,
1177
- byteLength: repository.bundle.byteLength
1178
- }
1179
- });
1180
- await writeTarEntry(archive, repositoryMetadataEntry, 384, metadata);
1181
- await writeTarEntry(archive, repositoryBundleEntry, 384, repository.bundle.bytes);
1182
- }
1183
- async function parseWorkspaceArchive(bytes, limits) {
1184
- if (bytes.byteLength > limits.maxArchiveBytes) {
1185
- throw new Error("candidate workspace archive exceeds maxArchiveBytes");
1186
- }
1187
- const parser = extract();
1188
- const files = [];
1189
- const observedEntryNames = /* @__PURE__ */ new Set();
1190
- const observedPaths = /* @__PURE__ */ new Set();
1191
- let totalBytes = 0;
1192
- let retainedEntryBytes = 0;
1193
- let previousPath;
1194
- let repositoryMetadata;
1195
- let repositoryBundle;
1196
- const decoded = (async () => {
1197
- for await (const entry of parser) {
1198
- const name = entry.header.name;
1199
- if (entry.header.type !== "file" || typeof name !== "string" || observedEntryNames.has(name)) {
1200
- throw new Error("candidate workspace tar contains an invalid entry");
1201
- }
1202
- observedEntryNames.add(name);
1203
- if (name.startsWith(workspaceEntryPrefix)) {
1204
- if (files.length >= limits.maxFiles) {
1205
- throw new Error("candidate workspace archive has an invalid file count");
1206
- }
1207
- const path = safeArchivePath(name.slice(workspaceEntryPrefix.length), limits.maxPathBytes);
1208
- if (entry.header.mode !== 420 && entry.header.mode !== 493) {
1209
- throw new Error(`candidate workspace archive has an unsupported mode: ${path}`);
1210
- }
1211
- assertRetainedArchiveSize(bytes.byteLength, retainedEntryBytes, entry.header.size, limits);
1212
- const fileBytes = await readTarEntry(entry, limits.maxFileBytes, `workspace file ${path}`);
1213
- retainedEntryBytes += fileBytes.byteLength;
1214
- assertWorkspacePathOrder(
1215
- path,
1216
- previousPath,
1217
- observedPaths,
1218
- "candidate workspace archive paths must be unique and sorted"
1219
- );
1220
- previousPath = path;
1221
- if (fileBytes.byteLength > limits.maxTotalFileBytes - totalBytes) {
1222
- throw new Error("candidate workspace archive exceeds maxTotalFileBytes");
1223
- }
1224
- totalBytes += fileBytes.byteLength;
1225
- files.push({ path, mode: entry.header.mode, bytes: fileBytes });
1226
- } else if (name === repositoryMetadataEntry) {
1227
- if (entry.header.mode !== 384 || repositoryMetadata) {
1228
- throw new Error("candidate workspace archive repository metadata is invalid");
1229
- }
1230
- assertRetainedArchiveSize(bytes.byteLength, retainedEntryBytes, entry.header.size, limits);
1231
- const metadataBytes = await readTarEntry(
1232
- entry,
1233
- limits.maxPathBytes,
1234
- "candidate repository metadata"
1235
- );
1236
- retainedEntryBytes += metadataBytes.byteLength;
1237
- repositoryMetadata = parseRepositoryMetadata(metadataBytes, limits.maxRepositoryBundleBytes);
1238
- } else if (name === repositoryBundleEntry) {
1239
- if (entry.header.mode !== 384 || repositoryBundle) {
1240
- throw new Error("candidate workspace archive repository bundle is invalid");
1241
- }
1242
- assertRetainedArchiveSize(bytes.byteLength, retainedEntryBytes, entry.header.size, limits);
1243
- repositoryBundle = await readTarEntry(
1244
- entry,
1245
- limits.maxRepositoryBundleBytes,
1246
- "candidate Git bundle"
1247
- );
1248
- retainedEntryBytes += repositoryBundle.byteLength;
1249
- } else {
1250
- throw new Error(`candidate workspace tar contains an unsupported entry: ${name}`);
1251
- }
1252
- }
1253
- if (Boolean(repositoryMetadata) !== Boolean(repositoryBundle)) {
1254
- throw new Error("candidate workspace archive repository evidence is incomplete");
1255
- }
1256
- const repository = repositoryMetadata && repositoryBundle ? repositoryFromTar(repositoryMetadata, repositoryBundle) : void 0;
1257
- return { files, ...repository ? { repository } : {} };
1258
- })();
1259
- parser.end(Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength));
1260
- const result = await decoded;
1261
- await verifyCanonicalWorkspaceArchive(
1262
- result.files,
1263
- result.repository,
1264
- bytes,
1265
- limits.maxArchiveBytes
1266
- );
1267
- return result;
1268
- }
1269
- function assertRetainedArchiveSize(archiveBytes, retainedEntryBytes, entryBytes, limits) {
1270
- if (!Number.isSafeInteger(entryBytes) || entryBytes === void 0 || entryBytes < 0 || entryBytes > limits.maxArchiveBytes - archiveBytes - retainedEntryBytes) {
1271
- throw new Error("candidate workspace archive exceeds maxArchiveBytes while decoding");
1009
+ if (!Buffer.from(canonicalCandidateBytes(parsed)).equals(Buffer.from(bytes))) {
1010
+ throw new Error(`candidate recovery ${label} bytes are not canonical`);
1272
1011
  }
1012
+ return parsed;
1273
1013
  }
1274
- function parseRepositoryMetadata(bytes, maxBundleBytes) {
1275
- let value;
1276
- try {
1277
- value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
1278
- } catch (error) {
1279
- throw new Error("candidate workspace archive repository metadata is invalid", { cause: error });
1280
- }
1281
- if (!isRecord(value) || Object.keys(value).sort().join(",") !== "bundle,headCommit,headTree,kind,schemaVersion" || value.schemaVersion !== 1 || value.kind !== archiveKind || typeof value.headCommit !== "string" || typeof value.headTree !== "string" || !isRecord(value.bundle) || Object.keys(value.bundle).sort().join(",") !== "byteLength,sha256" || typeof value.bundle.sha256 !== "string" || !/^sha256:[a-f0-9]{64}$/.test(value.bundle.sha256) || !Number.isSafeInteger(value.bundle.byteLength) || value.bundle.byteLength <= 0 || value.bundle.byteLength > maxBundleBytes) {
1282
- throw new Error("candidate workspace archive repository is invalid");
1283
- }
1284
- if (!Buffer.from(canonicalCandidateBytes(value)).equals(Buffer.from(bytes))) {
1285
- throw new Error("candidate workspace archive repository metadata is not canonical");
1286
- }
1287
- assertGitObjectId(value.headCommit, "repository HEAD");
1288
- assertGitObjectId(value.headTree, "repository HEAD tree");
1289
- if (value.headCommit.length !== value.headTree.length) {
1290
- throw new Error("candidate repository HEAD and tree use different object formats");
1291
- }
1292
- return {
1293
- schemaVersion: 1,
1294
- kind: archiveKind,
1295
- headCommit: value.headCommit,
1296
- headTree: value.headTree,
1297
- bundle: {
1298
- sha256: value.bundle.sha256,
1299
- byteLength: value.bundle.byteLength
1300
- }
1301
- };
1302
- }
1303
- function repositoryFromTar(metadata, bundle) {
1304
- verifyBytes(bundle, metadata.bundle.sha256, metadata.bundle.byteLength, "candidate Git bundle");
1305
- return {
1306
- headCommit: metadata.headCommit,
1307
- headTree: metadata.headTree,
1308
- bundle: { ...metadata.bundle, bytes: bundle }
1309
- };
1310
- }
1311
- async function writeTarEntry(archive, name, mode, bytes) {
1312
- await new Promise((resolveEntry, rejectEntry) => {
1313
- archive.entry(
1314
- {
1315
- name,
1316
- mode,
1317
- uid: 0,
1318
- gid: 0,
1319
- size: bytes.byteLength,
1320
- mtime: fixedTarTime,
1321
- type: "file",
1322
- uname: "",
1323
- gname: ""
1324
- },
1325
- Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength),
1326
- (error) => error ? rejectEntry(error) : resolveEntry()
1327
- );
1328
- });
1329
- }
1330
- async function readTarEntry(entry, maxBytes, label) {
1331
- const size = entry.header.size;
1332
- if (!Number.isSafeInteger(size) || size === void 0 || size < 0 || size > maxBytes) {
1333
- throw new Error(`${label} has an invalid tar size`);
1334
- }
1335
- const bytes = await collectStream(entry, maxBytes, label);
1336
- if (bytes.byteLength !== size) throw new Error(`${label} differs from its tar size`);
1337
- return bytes;
1338
- }
1339
- async function collectStream(stream, maxBytes, label) {
1340
- const chunks = [];
1341
- let totalBytes = 0;
1342
- for await (const chunk of stream) {
1343
- if (chunk.byteLength > maxBytes - totalBytes) {
1344
- const error = new Error(`${label} exceeds its size limit`);
1345
- if ("destroy" in stream && typeof stream.destroy === "function") stream.destroy(error);
1346
- throw error;
1347
- }
1348
- const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
1349
- chunks.push(bytes);
1350
- totalBytes += bytes.byteLength;
1351
- }
1352
- return Buffer.concat(chunks, totalBytes);
1353
- }
1354
- async function streamEqualsBytes(stream, expected, maxBytes, label) {
1355
- const expectedBuffer = Buffer.from(expected.buffer, expected.byteOffset, expected.byteLength);
1356
- let equal = true;
1357
- let offset = 0;
1358
- for await (const chunk of stream) {
1359
- if (chunk.byteLength > maxBytes - offset) {
1360
- const error = new Error(`${label} exceeds its size limit`);
1361
- if ("destroy" in stream && typeof stream.destroy === "function") stream.destroy(error);
1362
- throw error;
1363
- }
1364
- const observed = Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength);
1365
- if (offset + observed.byteLength > expectedBuffer.byteLength || !observed.equals(expectedBuffer.subarray(offset, offset + observed.byteLength))) {
1366
- equal = false;
1367
- }
1368
- offset += observed.byteLength;
1369
- }
1370
- return equal && offset === expectedBuffer.byteLength;
1371
- }
1372
- async function prepareEmptyDestination(destination) {
1373
- await mkdir2(destination, { recursive: true, mode: 448 });
1374
- const stats = await lstat(destination);
1375
- if (!stats.isDirectory() || stats.isSymbolicLink() || await realpath(destination) !== destination) {
1376
- throw new Error("candidate workspace destination must be a real directory");
1377
- }
1378
- if ((await readdir(destination)).length > 0) {
1379
- throw new Error("candidate workspace destination must be empty");
1380
- }
1381
- }
1382
- async function writeWorkspaceFiles(destination, files) {
1383
- for (const file of files) {
1384
- const path = workspacePath(destination, file.path);
1385
- await mkdir2(dirname(path), { recursive: true, mode: 448 });
1386
- const descriptor = await open2(
1387
- path,
1388
- fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL | (typeof fsConstants.O_NOFOLLOW === "number" ? fsConstants.O_NOFOLLOW : 0),
1389
- 384
1390
- );
1391
- try {
1392
- await descriptor.writeFile(file.bytes);
1393
- const stats = await descriptor.stat();
1394
- if (!stats.isFile() || stats.nlink !== 1) {
1395
- throw new Error(`candidate workspace file identity changed while writing: ${file.path}`);
1396
- }
1397
- await descriptor.chmod(file.mode);
1398
- } finally {
1399
- await descriptor.close();
1400
- }
1401
- }
1402
- }
1403
- async function materializeRepository(repository, destination) {
1404
- const temporary = await mkdtemp(join2(tmpdir(), "agent-candidate-workspace-bundle-"));
1405
- const bundlePath = join2(temporary, `${randomUUID2()}.bundle`);
1406
- try {
1407
- await writeFile(bundlePath, repository.bundle.bytes, { flag: "wx", mode: 384 });
1408
- await runCandidateGit(destination, [
1409
- "init",
1410
- "--quiet",
1411
- "--initial-branch=candidate",
1412
- `--object-format=${repository.headCommit.length === 64 ? "sha256" : "sha1"}`
1413
- ]);
1414
- await runCandidateGit(destination, ["bundle", "verify", bundlePath]);
1415
- await runCandidateGit(destination, ["bundle", "unbundle", bundlePath]);
1416
- const objectType = (await runCandidateGit(destination, ["cat-file", "-t", repository.headCommit])).stdout.toString("utf8").trim();
1417
- if (objectType !== "commit") throw new Error("candidate repository HEAD is not a commit");
1418
- const observedTree = (await runCandidateGit(destination, ["rev-parse", `${repository.headCommit}^{tree}`])).stdout.toString("utf8").trim();
1419
- if (observedTree !== repository.headTree) {
1420
- throw new Error("candidate repository bundle HEAD tree changed");
1421
- }
1422
- await runCandidateGit(destination, ["update-ref", "--no-deref", "HEAD", repository.headCommit]);
1423
- await runCandidateGit(destination, ["read-tree", repository.headTree]);
1424
- const status = (await runCandidateGit(destination, ["status", "--porcelain=v1", "--untracked-files=all"])).stdout.toString("utf8").trim();
1425
- if (status) throw new Error("candidate repository files do not exactly match HEAD");
1426
- } finally {
1427
- await rm(temporary, { recursive: true, force: true });
1428
- }
1429
- }
1430
- function workspaceManifest(files) {
1431
- return {
1432
- schemaVersion: 1,
1433
- kind: "agent-candidate-workspace-manifest",
1434
- files: files.map((file) => ({
1435
- path: file.path,
1436
- mode: file.mode,
1437
- sha256: sha256Bytes(file.bytes),
1438
- byteLength: file.bytes.byteLength
1439
- }))
1440
- };
1441
- }
1442
- function assertArchiveMatchesSnapshot(files, snapshot) {
1443
- const material = workspaceManifest(files);
1444
- const materialBytes = canonicalCandidateBytes(material);
1445
- verifyBytes(
1446
- materialBytes,
1447
- snapshot.manifest.sha256,
1448
- snapshot.manifest.byteLength,
1449
- "candidate workspace manifest"
1450
- );
1451
- if (canonicalCandidateDigest(material) !== snapshot.digest || !Buffer.from(materialBytes).equals(Buffer.from(canonicalCandidateBytes(snapshot.material)))) {
1452
- throw new Error("candidate workspace archive does not match its snapshot manifest");
1453
- }
1454
- }
1455
- function assertWorkspaceFilesWithinLimits(files, limits) {
1456
- if (files.length > limits.maxFiles) {
1457
- throw new Error("candidate workspace exceeds maxFiles");
1458
- }
1459
- let totalBytes = 0;
1460
- let previousPath;
1461
- const observedPaths = /* @__PURE__ */ new Set();
1462
- for (const file of files) {
1463
- const path = safeArchivePath(file.path, limits.maxPathBytes);
1464
- assertWorkspacePathOrder(
1465
- path,
1466
- previousPath,
1467
- observedPaths,
1468
- "candidate workspace paths must be unique and sorted"
1469
- );
1470
- previousPath = path;
1471
- if (file.mode !== 420 && file.mode !== 493) {
1472
- throw new Error(`candidate workspace file has unsupported mode: ${path}`);
1473
- }
1474
- if (file.bytes.byteLength > limits.maxFileBytes) {
1475
- throw new Error(`candidate workspace file exceeds maxFileBytes: ${path}`);
1476
- }
1477
- totalBytes += file.bytes.byteLength;
1478
- if (totalBytes > limits.maxTotalFileBytes) {
1479
- throw new Error("candidate workspace exceeds maxTotalFileBytes");
1480
- }
1481
- }
1482
- }
1483
- function normalizeWorkspaceFiles(input, limits) {
1484
- if (!Array.isArray(input)) {
1485
- throw new Error("candidate workspace files must be an array");
1486
- }
1487
- const fileCount = input.length;
1488
- if (fileCount > limits.maxFiles) {
1489
- throw new Error("candidate workspace exceeds maxFiles");
1490
- }
1491
- let totalBytes = 0;
1492
- const files = [];
1493
- for (let index = 0; index < fileCount; index++) {
1494
- const inputFile = input[index];
1495
- if (!isRecord(inputFile) || Object.keys(inputFile).sort().join(",") !== "bytes,mode,path") {
1496
- throw new Error(`candidate workspace file ${index} is invalid`);
1497
- }
1498
- const descriptors = Object.getOwnPropertyDescriptors(inputFile);
1499
- if (!("value" in (descriptors.path ?? {})) || !("value" in (descriptors.mode ?? {})) || !("value" in (descriptors.bytes ?? {}))) {
1500
- throw new Error(`candidate workspace file ${index} must use fixed values`);
1501
- }
1502
- const path = safeArchivePath(descriptors.path?.value, limits.maxPathBytes);
1503
- const mode = descriptors.mode?.value;
1504
- const inputBytes = descriptors.bytes?.value;
1505
- if (mode !== 420 && mode !== 493) {
1506
- throw new Error(`candidate workspace file has unsupported mode: ${path}`);
1507
- }
1508
- const view = inspectWorkspaceBytes(inputBytes, path);
1509
- if (view.byteLength > limits.maxFileBytes) {
1510
- throw new Error(`candidate workspace file exceeds maxFileBytes: ${path}`);
1511
- }
1512
- if (view.byteLength > limits.maxTotalFileBytes - totalBytes) {
1513
- throw new Error("candidate workspace exceeds maxTotalFileBytes");
1514
- }
1515
- if (view.byteLength > Math.max(0, limits.maxArchiveBytes - 1024)) {
1516
- throw new Error("candidate workspace archive exceeds maxArchiveBytes");
1517
- }
1518
- totalBytes += view.byteLength;
1519
- files.push({ path, mode, bytes: detachWorkspaceBytes(view) });
1520
- }
1521
- files.sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
1522
- assertWorkspaceFilesWithinLimits(files, limits);
1523
- return files;
1524
- }
1525
- function workspaceLimits(overrides) {
1526
- const limits = { ...defaultLimits, ...overrides };
1527
- for (const [name, value] of Object.entries(limits)) {
1528
- if (!Number.isSafeInteger(value) || value <= 0) {
1529
- throw new Error(`candidate workspace ${name} must be a positive safe integer`);
1530
- }
1531
- }
1532
- return Object.freeze(limits);
1533
- }
1534
- function safeArchivePath(value, maxPathBytes) {
1535
- if (typeof value !== "string" || !value || !isWellFormedUnicode(value) || Buffer.byteLength(value, "utf8") > maxPathBytes || isAbsolute(value) || win32.isAbsolute(value) || value.includes("\\") || value.includes("\0") || hasControlCharacter(value) || value.split("/").some((part) => !part || part === "." || part === "..") || [".git", ".sidecar"].includes(value.split("/")[0]?.toLowerCase() ?? "")) {
1536
- throw new Error(`candidate workspace archive contains an unsafe path: ${String(value)}`);
1537
- }
1538
- return value;
1539
- }
1540
- function assertWorkspacePathOrder(path, previousPath, observedPaths, errorMessage) {
1541
- if (previousPath !== void 0 && previousPath >= path) throw new Error(errorMessage);
1542
- for (let slash = path.indexOf("/"); slash !== -1; slash = path.indexOf("/", slash + 1)) {
1543
- if (observedPaths.has(path.slice(0, slash))) throw new Error(errorMessage);
1544
- }
1545
- observedPaths.add(path);
1546
- }
1547
- function inspectWorkspaceBytes(input, path) {
1548
- let buffer;
1549
- let byteOffset;
1550
- let byteLength;
1551
- try {
1552
- buffer = typedArrayBufferGetter.call(input);
1553
- byteOffset = typedArrayByteOffsetGetter.call(input);
1554
- byteLength = typedArrayByteLengthGetter.call(input);
1555
- } catch {
1556
- throw new Error(`candidate workspace file bytes are invalid: ${path}`);
1557
- }
1558
- if (isSharedArrayBuffer(buffer)) {
1559
- throw new Error(`candidate workspace file uses shared bytes: ${path}`);
1560
- }
1561
- if (!isAnyArrayBuffer(buffer)) {
1562
- throw new Error(`candidate workspace file bytes are invalid: ${path}`);
1563
- }
1564
- return { buffer, byteOffset, byteLength };
1565
- }
1566
- function detachWorkspaceBytes(input) {
1567
- const { buffer, byteOffset, byteLength } = input;
1568
- const copy = new Uint8Array(byteLength);
1569
- Uint8Array.prototype.set.call(copy, new Uint8Array(buffer, byteOffset, byteLength));
1570
- return copy;
1571
- }
1572
- function isWellFormedUnicode(value) {
1573
- for (let index = 0; index < value.length; index++) {
1574
- const code = value.charCodeAt(index);
1575
- if (code >= 55296 && code <= 56319) {
1576
- const next = value.charCodeAt(index + 1);
1577
- if (!(next >= 56320 && next <= 57343)) return false;
1578
- index++;
1579
- } else if (code >= 56320 && code <= 57343) {
1580
- return false;
1581
- }
1582
- }
1583
- return true;
1584
- }
1585
- function workspacePath(root, relativePath) {
1586
- const path = resolve(root, relativePath);
1587
- if (!path.startsWith(`${root}${sep}`)) {
1588
- throw new Error(`candidate workspace archive path escapes its destination: ${relativePath}`);
1589
- }
1590
- return path;
1591
- }
1592
- function assertGitObjectId(value, label) {
1593
- if (!/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(value)) {
1594
- throw new Error(`candidate repository ${label} is not a Git object id`);
1595
- }
1596
- }
1597
- function hasControlCharacter(value) {
1598
- for (let index = 0; index < value.length; index++) {
1599
- const code = value.charCodeAt(index);
1600
- if (code < 32 || code === 127) return true;
1601
- }
1602
- return false;
1603
- }
1604
- function isRecord(value) {
1605
- return typeof value === "object" && value !== null && !Array.isArray(value);
1606
- }
1607
- function requireTypedArrayGetter(name) {
1608
- const typedArrayPrototype = Object.getPrototypeOf(Uint8Array.prototype);
1609
- const getter = Object.getOwnPropertyDescriptor(typedArrayPrototype, name)?.get;
1610
- if (!getter) throw new Error(`typed array intrinsic ${name} accessor is unavailable`);
1611
- return getter;
1612
- }
1613
- var typedArrayBufferGetter = requireTypedArrayGetter("buffer");
1614
- var typedArrayByteOffsetGetter = requireTypedArrayGetter("byteOffset");
1615
- var typedArrayByteLengthGetter = requireTypedArrayGetter("byteLength");
1616
1014
 
1617
1015
  export {
1016
+ sealAgentCandidateBundle,
1618
1017
  buildAgentCandidateBundle,
1619
1018
  FileAgentCandidateExecutionClaimStore,
1620
1019
  disposePreparedAgentCandidateExecution,
1621
1020
  createProtectedAgentCandidateModelPort,
1622
- recoverExpiredAgentCandidateExecution,
1623
- captureAgentCandidateWorkspace,
1624
- captureAgentCandidateWorkspaceFiles,
1625
- createAgentCandidateWorkspacePort
1021
+ recoverExpiredAgentCandidateExecution
1626
1022
  };
1627
- //# sourceMappingURL=chunk-FTXM7Q7P.js.map
1023
+ //# sourceMappingURL=chunk-6YBA64Z2.js.map