rea-agents 1.6.0 → 2.0.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 (238) hide show
  1. package/README.md +157 -66
  2. package/bridge/ghidra/ReaGhidraBridge.java +2075 -0
  3. package/bridge/hopper_bridge.py +13 -3
  4. package/dist/application/AbortablePromise.js +25 -0
  5. package/dist/application/AnalysisProvider.js +3 -0
  6. package/dist/application/AnalysisProviderEvaluation.js +60 -0
  7. package/dist/application/AnalysisProviderRegistry.js +228 -0
  8. package/dist/application/AnalysisSnapshotCache.js +41 -21
  9. package/dist/application/AnalysisSnapshotFiles.js +5 -6
  10. package/dist/application/BinarySession.js +218 -101
  11. package/dist/application/CapabilityInventory.js +18 -0
  12. package/dist/application/CompositeProvider.js +20 -8
  13. package/dist/application/CrossVersionInvestigation.js +10 -6
  14. package/dist/application/DirectAnalysis.js +49 -35
  15. package/dist/application/Doctor.js +110 -8
  16. package/dist/application/ElectronBoundaryAnalysis.js +96 -0
  17. package/dist/application/ElectronBoundaryGraph.js +9 -0
  18. package/dist/application/ElectronBoundaryGraphContext.js +62 -0
  19. package/dist/application/ElectronBoundaryGraphIpc.js +242 -0
  20. package/dist/application/ElectronBoundaryGraphNative.js +86 -0
  21. package/dist/application/ElectronBoundaryGraphWindows.js +199 -0
  22. package/dist/application/EnhancedTools.js +39 -14
  23. package/dist/application/InvestigationProviders.js +41 -0
  24. package/dist/application/JavaScriptApplicationEvidence.js +26 -0
  25. package/dist/application/JavaScriptApplicationEvidenceGraph.js +89 -0
  26. package/dist/application/JavaScriptApplicationService.js +74 -0
  27. package/dist/application/JavaScriptApplicationWorkflowEvidence.js +29 -0
  28. package/dist/application/JavaScriptApplicationWorkflowService.js +90 -0
  29. package/dist/application/JavaScriptArtifactAnalysis.js +305 -0
  30. package/dist/application/JavaScriptArtifactAnalysisTypes.js +1 -0
  31. package/dist/application/JavaScriptArtifactFiles.js +231 -0
  32. package/dist/application/JavaScriptArtifactGraphAccumulator.js +54 -0
  33. package/dist/application/JavaScriptArtifactGraphBuilder.js +144 -0
  34. package/dist/application/JavaScriptArtifactGraphContext.js +205 -0
  35. package/dist/application/JavaScriptArtifactGraphDocuments.js +108 -0
  36. package/dist/application/JavaScriptArtifactGraphEvidence.js +99 -0
  37. package/dist/application/JavaScriptArtifactGraphFindings.js +271 -0
  38. package/dist/application/JavaScriptArtifactGraphStructure.js +409 -0
  39. package/dist/application/JavaScriptArtifactReconstruction.js +77 -0
  40. package/dist/application/JavaScriptArtifactReconstructionInput.js +18 -0
  41. package/dist/application/JavaScriptReplayPlanning.js +201 -0
  42. package/dist/application/JavaScriptReplayService.js +266 -0
  43. package/dist/application/JavaScriptRuntimeReconciliationEvidence.js +25 -0
  44. package/dist/application/JavaScriptRuntimeReconciliationService.js +27 -0
  45. package/dist/application/MacHopper.js +20 -4
  46. package/dist/application/ManagedApplicationGraphService.js +55 -0
  47. package/dist/application/ManagedMemberComparisonService.js +122 -0
  48. package/dist/application/ManagedNativeVerificationService.js +39 -0
  49. package/dist/application/ManagedReconstructionService.js +58 -0
  50. package/dist/application/ManagedRuntimeCorrelationService.js +103 -0
  51. package/dist/application/PermissionAuthority.js +1 -1
  52. package/dist/application/ProcessCaptureLifecycle.js +1 -1
  53. package/dist/application/ProcessHarness.js +1 -1
  54. package/dist/application/ProcessSampling.js +16 -12
  55. package/dist/application/ProjectPermissionStore.js +1 -0
  56. package/dist/application/SessionProviderRouter.js +219 -0
  57. package/dist/application/Setup.js +84 -60
  58. package/dist/application/SetupClients.js +1 -1
  59. package/dist/application/SetupPlan.js +9 -2
  60. package/dist/application/SupportedClients.js +40 -16
  61. package/dist/application/Upgrade.js +1 -1
  62. package/dist/application/runtime.js +11 -6
  63. package/dist/artifacts/AsarArtifactReader.js +23 -3
  64. package/dist/browser/CdpBrowserProvider.js +4 -2
  65. package/dist/browser/CdpCaptureEvents.js +49 -0
  66. package/dist/browser/CdpElectronInspection.js +106 -162
  67. package/dist/browser/CdpElectronProvider.js +9 -3
  68. package/dist/browser/CdpElectronScriptEvents.js +69 -0
  69. package/dist/browser/CdpElectronScripts.js +94 -0
  70. package/dist/browser/CdpElectronWorkers.js +56 -0
  71. package/dist/browser/CdpEndpoint.js +14 -1
  72. package/dist/browser/CdpPageCapture.js +8 -4
  73. package/dist/catalogIdentity.js +6 -42
  74. package/dist/cli.js +446 -34
  75. package/dist/cliApplicationCommands.js +62 -0
  76. package/dist/cliBrowserAdvancedCommands.js +5 -4
  77. package/dist/cliBrowserCommands.js +5 -4
  78. package/dist/cliCommandNames.js +56 -0
  79. package/dist/cliElectronCommands.js +106 -3
  80. package/dist/cliEvidenceCommands.js +4 -3
  81. package/dist/cliInvestigationCommands.js +2 -1
  82. package/dist/cliJsonInput.js +54 -0
  83. package/dist/cliPolicyCommands.js +2 -1
  84. package/dist/cliProcessCommands.js +3 -2
  85. package/dist/config.js +101 -2
  86. package/dist/contracts/applicationToolContracts.js +91 -0
  87. package/dist/contracts/artifactToolContracts.js +5 -9
  88. package/dist/contracts/browserToolContracts.js +14 -62
  89. package/dist/contracts/electronToolContracts.js +45 -19
  90. package/dist/contracts/enhancedInputs.js +2 -2
  91. package/dist/contracts/errorSchemas.js +1 -0
  92. package/dist/contracts/investigationExamples.js +0 -6
  93. package/dist/contracts/javascriptApplicationWorkflowExamples.js +46 -0
  94. package/dist/contracts/javascriptRuntimeReconciliationExample.js +208 -0
  95. package/dist/contracts/managedToolContracts.js +153 -0
  96. package/dist/contracts/managedWorkflowExamples.js +346 -0
  97. package/dist/contracts/managedWorkflowToolContracts.js +203 -0
  98. package/dist/contracts/nativeToolContracts.js +2 -6
  99. package/dist/contracts/promptContracts.js +18 -2
  100. package/dist/contracts/providerSelection.js +28 -0
  101. package/dist/contracts/sessionLifecycleInputs.js +2 -0
  102. package/dist/contracts/toolContractExamples.js +10 -25
  103. package/dist/contracts/toolContracts.js +69 -65
  104. package/dist/contracts/toolEffects.js +160 -0
  105. package/dist/contracts/toolOutputSchemas.js +152 -42
  106. package/dist/doctorRuntime.js +47 -0
  107. package/dist/domain/analysisProfile.js +78 -0
  108. package/dist/domain/analysisSnapshot.js +92 -46
  109. package/dist/domain/artifactComparison.js +3 -4
  110. package/dist/domain/artifactGraph.js +1 -0
  111. package/dist/domain/binaryTarget.js +4 -29
  112. package/dist/domain/browserObservation.js +3 -0
  113. package/dist/domain/bundleComparison.js +4 -4
  114. package/dist/domain/electronObservation.js +13 -0
  115. package/dist/domain/electronStaticAnalysis.js +9 -0
  116. package/dist/domain/electronStaticAnalysisBrowser.js +194 -0
  117. package/dist/domain/electronStaticAnalysisIpc.js +171 -0
  118. package/dist/domain/electronStaticAnalysisNative.js +164 -0
  119. package/dist/domain/electronStaticAnalysisTypes.js +1 -0
  120. package/dist/domain/electronStaticAnalysisValues.js +103 -0
  121. package/dist/domain/errors.js +148 -7
  122. package/dist/domain/evidence.js +44 -5
  123. package/dist/domain/evidenceBundle.js +9 -7
  124. package/dist/domain/functionComparison.js +9 -3
  125. package/dist/domain/functionComparisonNormalization.js +37 -0
  126. package/dist/domain/functionComparisonSchemas.js +3 -8
  127. package/dist/domain/functionDossierEvidence.js +4 -1
  128. package/dist/domain/hopperValues.js +66 -9
  129. package/dist/domain/inputIssueProjection.js +52 -0
  130. package/dist/domain/investigationWorkspace.js +2 -1
  131. package/dist/domain/javascriptApplicationAnalysis.js +155 -0
  132. package/dist/domain/javascriptApplicationChangeGraph.js +231 -0
  133. package/dist/domain/javascriptApplicationEvidenceSchemas.js +379 -0
  134. package/dist/domain/javascriptApplicationGraph.js +282 -0
  135. package/dist/domain/javascriptApplicationGraphSchemas.js +139 -0
  136. package/dist/domain/javascriptApplicationVersionComparison.js +116 -0
  137. package/dist/domain/javascriptApplicationVersionComparisonSchemas.js +139 -0
  138. package/dist/domain/javascriptApplicationVersionItems.js +221 -0
  139. package/dist/domain/javascriptApplicationVersionKeys.js +170 -0
  140. package/dist/domain/javascriptAstFingerprint.js +143 -0
  141. package/dist/domain/javascriptFeatureSeed.js +140 -0
  142. package/dist/domain/javascriptFeatureTrace.js +232 -0
  143. package/dist/domain/javascriptFeatureTraceSchemas.js +136 -0
  144. package/dist/domain/javascriptFeatureTraversal.js +88 -0
  145. package/dist/domain/javascriptNativeHandoff.js +85 -0
  146. package/dist/domain/javascriptReplay.js +361 -0
  147. package/dist/domain/javascriptRuntimeLoadState.js +106 -0
  148. package/dist/domain/javascriptRuntimeReconciliation.js +42 -0
  149. package/dist/domain/javascriptRuntimeReconciliationGraph.js +169 -0
  150. package/dist/domain/javascriptRuntimeReconciliationMatching.js +247 -0
  151. package/dist/domain/javascriptRuntimeReconciliationParsing.js +160 -0
  152. package/dist/domain/javascriptRuntimeReconciliationResult.js +119 -0
  153. package/dist/domain/javascriptRuntimeReconciliationRuntime.js +316 -0
  154. package/dist/domain/javascriptRuntimeReconciliationSchemas.js +245 -0
  155. package/dist/domain/javascriptRuntimeStaticCandidates.js +191 -0
  156. package/dist/domain/javascriptStaticAnalysis.js +346 -0
  157. package/dist/domain/javascriptStaticAnalysisFindings.js +39 -0
  158. package/dist/domain/javascriptStaticAnalysisHelpers.js +274 -0
  159. package/dist/domain/javascriptStaticAnalysisState.js +23 -0
  160. package/dist/domain/javascriptStaticAnalysisTypes.js +1 -0
  161. package/dist/domain/managedApplicationGraph.js +556 -0
  162. package/dist/domain/managedArtifact.js +444 -0
  163. package/dist/domain/managedMemberComparison.js +565 -0
  164. package/dist/domain/managedNativeVerification.js +408 -0
  165. package/dist/domain/managedReconstruction.js +183 -0
  166. package/dist/domain/managedRuntimeCorrelation.js +175 -0
  167. package/dist/domain/reconstructionVerificationSchemas.js +1 -3
  168. package/dist/dotnet/ManagedArtifactInspector.js +318 -0
  169. package/dist/dotnet/ManagedMemberInspector.js +887 -0
  170. package/dist/dotnet/ManagedMetadataHeaps.js +131 -0
  171. package/dist/dotnet/ManagedMetadataInventory.js +368 -0
  172. package/dist/dotnet/ManagedMetadataLayout.js +322 -0
  173. package/dist/dotnet/ManagedNativeBoundaryInspector.js +391 -0
  174. package/dist/dotnet/ManagedPeReader.js +157 -0
  175. package/dist/dotnet/ManagedReaderFailure.js +10 -0
  176. package/dist/dotnet/ManagedStaticProvider.js +203 -0
  177. package/dist/generatedPackageMetadata.js +2 -2
  178. package/dist/ghidra/GhidraAnalysisProfile.js +37 -0
  179. package/dist/ghidra/GhidraClient.js +400 -0
  180. package/dist/ghidra/GhidraClientTypes.js +1 -0
  181. package/dist/ghidra/GhidraDefaults.js +20 -0
  182. package/dist/ghidra/GhidraDiagnostics.js +43 -0
  183. package/dist/ghidra/GhidraDoctor.js +59 -0
  184. package/dist/ghidra/GhidraFunctionValues.js +264 -0
  185. package/dist/ghidra/GhidraInstallation.js +239 -0
  186. package/dist/ghidra/GhidraInventoryValues.js +217 -0
  187. package/dist/ghidra/GhidraLauncher.js +139 -0
  188. package/dist/ghidra/GhidraProvider.js +366 -0
  189. package/dist/ghidra/GhidraRequestQueue.js +98 -0
  190. package/dist/ghidra/GhidraResponseBuffer.js +31 -0
  191. package/dist/ghidra/GhidraResponseRouter.js +24 -0
  192. package/dist/ghidra/GhidraSessionError.js +39 -0
  193. package/dist/ghidra/GhidraSessionValues.js +62 -0
  194. package/dist/ghidra/GhidraSocketConnection.js +46 -0
  195. package/dist/ghidra/protocol.js +69 -0
  196. package/dist/hopper/BridgeLauncher.js +19 -54
  197. package/dist/hopper/HopperAnalysisProfile.js +106 -0
  198. package/dist/hopper/HopperClient.js +154 -162
  199. package/dist/hopper/HopperProvider.js +99 -7
  200. package/dist/hopper/HopperSessionValues.js +27 -0
  201. package/dist/hopper/HopperSocketConnection.js +31 -0
  202. package/dist/main.js +14 -0
  203. package/dist/native/NativeMacOSProvider.js +3 -1
  204. package/dist/process/PendingOperations.js +59 -0
  205. package/dist/process/PrivateRuntimeRoot.js +33 -0
  206. package/dist/process/ProviderDeadline.js +95 -0
  207. package/dist/process/ProviderProcess.js +275 -0
  208. package/dist/replay/JavaScriptReplayWorker.js +286 -0
  209. package/dist/replay/LinuxJavaScriptReplayRunner.js +434 -0
  210. package/dist/replay/LinuxRuntimeClosure.js +46 -0
  211. package/dist/replay/LinuxSeccompPolicy.js +40 -0
  212. package/dist/replay/ReplayWorkerProtocol.js +55 -0
  213. package/dist/replay/SystemJavaScriptReplayHost.js +182 -0
  214. package/dist/server/createServer.js +53 -0
  215. package/dist/server/promptCompletion.js +9 -3
  216. package/dist/server/registerApplicationTools.js +115 -0
  217. package/dist/server/registerArtifactComparisonTool.js +16 -9
  218. package/dist/server/registerBrowserTools.js +33 -8
  219. package/dist/server/registerBundleComparisonTool.js +17 -20
  220. package/dist/server/registerElectronTools.js +45 -3
  221. package/dist/server/registerEnhancedTools.js +67 -19
  222. package/dist/server/registerEvidenceTools.js +5 -1
  223. package/dist/server/registerFunctionComparisonTool.js +14 -7
  224. package/dist/server/registerInvestigationTools.js +21 -17
  225. package/dist/server/registerManagedTools.js +6 -0
  226. package/dist/server/registerManagedWorkflowTools.js +277 -0
  227. package/dist/server/registerOfficialTools.js +11 -8
  228. package/dist/server/registerProcessComparisonTool.js +25 -47
  229. package/dist/server/registerSessionStatusTool.js +7 -7
  230. package/dist/server/registerSessionTools.js +57 -87
  231. package/dist/server/sessionEvidence.js +14 -19
  232. package/dist/server/toolInputValidation.js +77 -0
  233. package/dist/server/toolRegistrationOptions.js +79 -2
  234. package/dist/server/toolResult.js +25 -17
  235. package/package.json +17 -7
  236. package/scripts/rea.mjs +36 -2
  237. package/skills/rea-analysis/SKILL.md +59 -3
  238. /package/dist/{application → process}/ProcessOwnership.js +0 -0
@@ -0,0 +1,182 @@
1
+ import { constants } from "node:fs";
2
+ import { mkdtemp, open, readFile, realpath, rm, stat, writeFile, } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { spawn } from "node:child_process";
7
+ import { digestBytes } from "../application/JavaScriptReplayPlanning.js";
8
+ import { buildLinuxX64ReplaySeccomp, linuxX64ReplaySeccompDigest, } from "./LinuxSeccompPolicy.js";
9
+ import { resolveLinuxRuntimeClosure } from "./LinuxRuntimeClosure.js";
10
+ const VERSION_OUTPUT_LIMIT = 16 * 1024;
11
+ /** Production planning host for descriptor-backed reads and fail-closed probes. */
12
+ export class SystemJavaScriptReplayHost {
13
+ async readSource(path, maximumBytes) {
14
+ const canonicalPath = await realpath(path);
15
+ const handle = await open(canonicalPath, constants.O_RDONLY | constants.O_NOFOLLOW);
16
+ try {
17
+ const metadata = await handle.stat();
18
+ if (!metadata.isFile() || metadata.size > maximumBytes)
19
+ throw new RangeError(`Replay source exceeds its declared limit: ${canonicalPath}`);
20
+ const bytes = await handle.readFile();
21
+ return { canonicalPath, bytes };
22
+ }
23
+ finally {
24
+ await handle.close();
25
+ }
26
+ }
27
+ async identifyExecutable(path, versionArguments) {
28
+ const canonicalPath = await realpath(path);
29
+ const metadata = await stat(canonicalPath);
30
+ if (!metadata.isFile())
31
+ throw new TypeError(`Replay executable is not a file: ${canonicalPath}`);
32
+ const [bytes, version] = await Promise.all([
33
+ readFile(canonicalPath),
34
+ capture(canonicalPath, versionArguments, VERSION_OUTPUT_LIMIT),
35
+ ]);
36
+ return {
37
+ path: canonicalPath,
38
+ version: version.trim(),
39
+ sha256: digestBytes(bytes),
40
+ };
41
+ }
42
+ identifyWorker() {
43
+ return this.identifyExecutable(fileURLToPath(new URL("./JavaScriptReplayWorker.js", import.meta.url)), []);
44
+ }
45
+ identifyRuntimeClosure(nodePath) {
46
+ return resolveLinuxRuntimeClosure(nodePath);
47
+ }
48
+ seccompDigest() {
49
+ return linuxX64ReplaySeccompDigest();
50
+ }
51
+ async probe(policy) {
52
+ if (process.platform !== "linux" || process.arch !== "x64")
53
+ throw new TypeError("Controlled replay requires Linux x86_64 in v1");
54
+ const bwrapMetadata = await stat(policy.bubblewrapPath);
55
+ if ((bwrapMetadata.mode & 0o4000) !== 0)
56
+ throw new TypeError("Setuid Bubblewrap is not admitted by replay policy");
57
+ if ((await readFile("/sys/fs/cgroup/cgroup.controllers", "utf8")).trim()
58
+ .length === 0)
59
+ throw new TypeError("Controlled replay requires delegated cgroup v2");
60
+ const directory = await mkdtemp(join(tmpdir(), "rea-replay-probe-"));
61
+ const filterPath = join(directory, "seccomp.bpf");
62
+ await writeFile(filterPath, buildLinuxX64ReplaySeccomp(), { mode: 0o600 });
63
+ const filter = await open(filterPath, "r");
64
+ try {
65
+ await run(policy.bubblewrapPath, [
66
+ "--unshare-all",
67
+ "--unshare-user",
68
+ "--disable-userns",
69
+ "--new-session",
70
+ "--die-with-parent",
71
+ "--cap-drop",
72
+ "ALL",
73
+ "--ro-bind",
74
+ "/usr",
75
+ "/usr",
76
+ "--ro-bind",
77
+ "/lib",
78
+ "/lib",
79
+ "--ro-bind-try",
80
+ "/lib64",
81
+ "/lib64",
82
+ "--proc",
83
+ "/proc",
84
+ "--dev",
85
+ "/dev",
86
+ "--seccomp",
87
+ "3",
88
+ "--",
89
+ "/usr/bin/true",
90
+ ], ["ignore", "pipe", "pipe", filter.fd]);
91
+ await runExpectFailure(policy.bubblewrapPath, [
92
+ "--unshare-all",
93
+ "--unshare-user",
94
+ "--disable-userns",
95
+ "--new-session",
96
+ "--die-with-parent",
97
+ "--cap-drop",
98
+ "ALL",
99
+ "--ro-bind",
100
+ "/usr",
101
+ "/usr",
102
+ "--ro-bind",
103
+ "/lib",
104
+ "/lib",
105
+ "--ro-bind-try",
106
+ "/lib64",
107
+ "/lib64",
108
+ "--proc",
109
+ "/proc",
110
+ "--dev",
111
+ "/dev",
112
+ "--seccomp",
113
+ "3",
114
+ "--",
115
+ "/usr/bin/unshare",
116
+ "--mount",
117
+ "/usr/bin/true",
118
+ ], ["ignore", "pipe", "pipe", filter.fd]);
119
+ await run(policy.systemdRunPath, [
120
+ "--user",
121
+ "--pipe",
122
+ "--wait",
123
+ "--service-type=exec",
124
+ "--quiet",
125
+ "--property=MemoryMax=16777216",
126
+ "--property=MemorySwapMax=0",
127
+ "--property=TasksMax=2",
128
+ "--",
129
+ "/usr/bin/true",
130
+ ]);
131
+ }
132
+ finally {
133
+ await filter.close();
134
+ await rm(directory, { recursive: true, force: true });
135
+ }
136
+ }
137
+ }
138
+ const capture = async (executable, arguments_, maximumBytes) => {
139
+ if (arguments_.length === 0)
140
+ return "content-addressed-worker-v1";
141
+ const result = await collect(executable, arguments_, maximumBytes);
142
+ if (result.code !== 0)
143
+ throw new TypeError(`Replay executable probe failed: ${executable}`);
144
+ return result.stdout.length > 0 ? result.stdout : result.stderr;
145
+ };
146
+ const run = async (executable, arguments_, stdio) => {
147
+ const result = await collect(executable, arguments_, VERSION_OUTPUT_LIMIT, stdio);
148
+ if (result.code !== 0)
149
+ throw new TypeError(`Replay feature probe failed (${String(result.code)}): ${executable}: ${result.stderr.trim()}`);
150
+ };
151
+ const runExpectFailure = async (executable, arguments_, stdio) => {
152
+ const result = await collect(executable, arguments_, VERSION_OUTPUT_LIMIT, stdio);
153
+ if (result.code === 0)
154
+ throw new TypeError(`Replay denial probe unexpectedly succeeded: ${executable}`);
155
+ };
156
+ const collect = async (executable, arguments_, maximumBytes, stdio = ["ignore", "pipe", "pipe"]) => new Promise((resolve, reject) => {
157
+ const child = spawn(executable, [...arguments_], { stdio });
158
+ const timeout = setTimeout(() => child.kill("SIGKILL"), 5_000);
159
+ timeout.unref();
160
+ let stdout = "";
161
+ let stderr = "";
162
+ const retain = (current, chunk) => {
163
+ const remaining = Math.max(0, maximumBytes - Buffer.byteLength(current));
164
+ return current + chunk.subarray(0, remaining).toString("utf8");
165
+ };
166
+ if (child.stdout !== null)
167
+ child.stdout.on("data", (chunk) => {
168
+ stdout = retain(stdout, chunk);
169
+ });
170
+ if (child.stderr !== null)
171
+ child.stderr.on("data", (chunk) => {
172
+ stderr = retain(stderr, chunk);
173
+ });
174
+ child.once("error", (error) => {
175
+ clearTimeout(timeout);
176
+ reject(error);
177
+ });
178
+ child.once("close", (code) => {
179
+ clearTimeout(timeout);
180
+ resolve({ code, stdout, stderr });
181
+ });
182
+ });
@@ -5,12 +5,17 @@ import { registerOfficialTools } from "./registerOfficialTools.js";
5
5
  import { registerSessionTools } from "./registerSessionTools.js";
6
6
  import { registerNativeTools } from "./registerNativeTools.js";
7
7
  import { registerArtifactTools } from "./registerArtifactTools.js";
8
+ import { registerManagedTools } from "./registerManagedTools.js";
9
+ import { registerManagedWorkflowTools } from "./registerManagedWorkflowTools.js";
8
10
  import { silentLogger } from "../logger.js";
9
11
  import { registerGuidedPrompts } from "./registerPrompts.js";
10
12
  import { registerEvidenceResources } from "./registerEvidenceResources.js";
11
13
  import { createServerIdentity } from "../serverIdentity.js";
12
14
  import { registerBrowserTools } from "./registerBrowserTools.js";
13
15
  import { registerElectronTools } from "./registerElectronTools.js";
16
+ import { registerApplicationTools } from "./registerApplicationTools.js";
17
+ import { LinuxJavaScriptReplayRunner } from "../replay/LinuxJavaScriptReplayRunner.js";
18
+ import { SystemJavaScriptReplayHost } from "../replay/SystemJavaScriptReplayHost.js";
14
19
  /**
15
20
  * Construct one MCP server without acquiring subprocess resources.
16
21
  * Supplying a session adds target lifecycle tools; omitting it retains the
@@ -66,6 +71,14 @@ export const createServer = (analysis, session, options = {}) => {
66
71
  server.sendResourceListChanged();
67
72
  return recorded;
68
73
  };
74
+ const recordEvidenceWithUnknown = session === undefined
75
+ ? undefined
76
+ : (evidence, input) => {
77
+ const recorded = session.recordEvidenceWithUnknown(evidence, input);
78
+ if (recorded.ok)
79
+ server.sendResourceListChanged();
80
+ return recorded;
81
+ };
69
82
  registerOfficialTools(server, analysis, {
70
83
  logger: toolLogger,
71
84
  activeTarget,
@@ -77,6 +90,7 @@ export const createServer = (analysis, session, options = {}) => {
77
90
  registerEnhancedTools(server, analysis, {
78
91
  logger: toolLogger,
79
92
  activeTarget,
93
+ analysisProfile: session === undefined ? undefined : () => session.analysisProfile(),
80
94
  recordEvidence,
81
95
  recordUnknown: session === undefined
82
96
  ? undefined
@@ -95,6 +109,26 @@ export const createServer = (analysis, session, options = {}) => {
95
109
  ? {}
96
110
  : { permissionAuthority: options.permissionAuthority }),
97
111
  });
112
+ registerManagedTools(server, analysis, {
113
+ logger: toolLogger,
114
+ activeTarget,
115
+ recordEvidence,
116
+ });
117
+ if (session !== undefined)
118
+ registerManagedWorkflowTools(server, {
119
+ logger: toolLogger,
120
+ recordEvidence,
121
+ recordEvidenceWithUnknown,
122
+ session,
123
+ runtime: {
124
+ policy: options.managedRuntimePolicy ?? {
125
+ enabled: false,
126
+ roots: [],
127
+ executablePath: "/usr/bin/dotnet",
128
+ },
129
+ authority: options.permissionAuthority,
130
+ },
131
+ });
98
132
  registerBrowserTools(server, {
99
133
  logger: toolLogger,
100
134
  browser: options.browserObservation,
@@ -107,6 +141,25 @@ export const createServer = (analysis, session, options = {}) => {
107
141
  permissionAuthority: options.permissionAuthority,
108
142
  recordEvidence,
109
143
  });
144
+ registerApplicationTools(server, {
145
+ logger: toolLogger,
146
+ recordEvidence,
147
+ recordEvidenceWithUnknown,
148
+ replay: {
149
+ policy: options.javascriptReplayPolicy ?? {
150
+ enabled: false,
151
+ roots: [],
152
+ nodePath: process.execPath,
153
+ bubblewrapPath: "/usr/bin/bwrap",
154
+ systemdRunPath: "/usr/bin/systemd-run",
155
+ systemctlPath: "/usr/bin/systemctl",
156
+ shellPath: "/usr/bin/bash",
157
+ },
158
+ host: options.javascriptReplayHost ?? new SystemJavaScriptReplayHost(),
159
+ runner: options.javascriptReplayRunner ?? new LinuxJavaScriptReplayRunner(),
160
+ authority: options.permissionAuthority,
161
+ },
162
+ });
110
163
  registerGuidedPrompts(server, analysis, session);
111
164
  if (session !== undefined) {
112
165
  registerEvidenceResources(server, session);
@@ -8,6 +8,9 @@ const MAX_COMPLETION_VALUE_LENGTH = 4_096;
8
8
  const documentListSchema = z.array(z.string().min(1));
9
9
  const providerStatusSchema = z.object({
10
10
  providers: z.array(z.object({ id: z.string().min(1) })),
11
+ analysis_provider_candidates: z
12
+ .array(z.object({ provider: z.object({ id: z.string().min(1) }) }))
13
+ .optional(),
11
14
  });
12
15
  /** Build bounded live completion over the active provider and session ledger. */
13
16
  export const createPromptCompletionSource = (analysis, session) => ({
@@ -94,9 +97,12 @@ const providerCandidates = (session) => {
94
97
  if (session === undefined)
95
98
  return [];
96
99
  const parsed = providerStatusSchema.safeParse(session.status());
97
- return parsed.success
98
- ? parsed.data.providers.map(({ id }) => id).slice(0, COMPLETION_SCAN_LIMIT)
99
- : [];
100
+ if (!parsed.success)
101
+ return [];
102
+ const candidates = (parsed.data.analysis_provider_candidates?.length ?? 0) > 0
103
+ ? (parsed.data.analysis_provider_candidates?.map(({ provider }) => provider.id) ?? [])
104
+ : parsed.data.providers.map(({ id }) => id);
105
+ return ["auto", ...candidates].slice(0, COMPLETION_SCAN_LIMIT);
100
106
  };
101
107
  const evidenceCandidates = (kind, session) => {
102
108
  if (session === undefined)
@@ -0,0 +1,115 @@
1
+ import { compareApplicationVersionsEvidenceValidated, traceApplicationFeatureEvidenceValidated, } from "../application/JavaScriptApplicationWorkflowService.js";
2
+ import { runControlledReplayValidated, } from "../application/JavaScriptReplayService.js";
3
+ import { APPLICATION_TOOL_CONTRACTS } from "../contracts/applicationToolContracts.js";
4
+ import { compareApplicationVersionsInputSchema } from "../domain/javascriptApplicationVersionComparisonSchemas.js";
5
+ import { applicationVersionComparisonResultSchema } from "../domain/javascriptApplicationVersionComparisonSchemas.js";
6
+ import { traceApplicationFeatureInputSchema } from "../domain/javascriptFeatureTraceSchemas.js";
7
+ import { parseEvidence } from "../domain/evidence.js";
8
+ import { controlledReplayInputSchema, controlledReplayOutputSchema, } from "../domain/javascriptReplay.js";
9
+ import { logToolExecution } from "./toolLogging.js";
10
+ import { toCallToolResult } from "./toolResult.js";
11
+ import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
12
+ import { safeParseToolInput } from "./toolInputValidation.js";
13
+ import { mcpProgressReporter } from "./mcpProgress.js";
14
+ /** Register provider-neutral JavaScript application graph workflows. */
15
+ export const registerApplicationTools = (server, options) => {
16
+ const [traceContract, compareContract, replayContract] = APPLICATION_TOOL_CONTRACTS;
17
+ server.registerTool(traceContract.name, toolRegistrationOptions(traceContract), async (input) => {
18
+ const parsedInput = safeParseToolInput(traceApplicationFeatureInputSchema, input, traceContract.name);
19
+ if (!parsedInput.ok)
20
+ return toCallToolResult(parsedInput, traceContract);
21
+ const parsed = parsedInput.value;
22
+ const result = await logToolExecution(options.logger, traceContract.name, () => Promise.resolve(traceApplicationFeatureEvidenceValidated(parsed)));
23
+ if (!result.ok)
24
+ return toCallToolResult(result, traceContract);
25
+ const sources = [parsed.application, ...parsed.native_observations];
26
+ const recorded = recordSources(options.recordEvidence, sources);
27
+ if (!recorded.ok)
28
+ return toCallToolResult(recorded, traceContract);
29
+ return recordResult(options, traceContract, result.value);
30
+ });
31
+ server.registerTool(compareContract.name, toolRegistrationOptions(compareContract), async (input) => {
32
+ const parsedInput = safeParseToolInput(compareApplicationVersionsInputSchema, input, compareContract.name);
33
+ if (!parsedInput.ok)
34
+ return toCallToolResult(parsedInput, compareContract);
35
+ const parsed = parsedInput.value;
36
+ const result = await logToolExecution(options.logger, compareContract.name, () => Promise.resolve(compareApplicationVersionsEvidenceValidated(parsed)));
37
+ if (!result.ok)
38
+ return toCallToolResult(result, compareContract);
39
+ const sources = [
40
+ parsed.left,
41
+ parsed.right,
42
+ ...parsed.left_native_observations,
43
+ ...parsed.right_native_observations,
44
+ ];
45
+ const recorded = recordSources(options.recordEvidence, sources);
46
+ if (!recorded.ok)
47
+ return toCallToolResult(recorded, compareContract);
48
+ const comparison = applicationVersionComparisonResultSchema.parse(result.value.normalized_result);
49
+ const unknown = comparison.summary.unknown > 0;
50
+ return recordResult(options, compareContract, result.value, parsed.unknown_registry_approved === true && unknown);
51
+ });
52
+ server.registerTool(replayContract.name, toolRegistrationOptions(replayContract), async (input, context) => {
53
+ const parsedInput = safeParseToolInput(controlledReplayInputSchema, input, replayContract.name);
54
+ if (!parsedInput.ok)
55
+ return toCallToolResult(parsedInput, replayContract);
56
+ const parsed = parsedInput.value;
57
+ const result = await logToolExecution(options.logger, replayContract.name, () => runControlledReplayValidated(options.replay, parsed, {
58
+ signal: context.mcpReq.signal,
59
+ progress: mcpProgressReporter(context),
60
+ }));
61
+ if (!result.ok)
62
+ return toCallToolResult(result, replayContract);
63
+ const output = controlledReplayOutputSchema.parse(result.value);
64
+ if (output.evidence !== null) {
65
+ const sourcesRecorded = recordSources(options.recordEvidence, output.source_evidence.map((item) => parseEvidence(item)));
66
+ if (!sourcesRecorded.ok)
67
+ return toCallToolResult(sourcesRecorded, replayContract);
68
+ const recorded = options.recordEvidence?.(parseEvidence(output.evidence));
69
+ if (recorded !== undefined && !recorded.ok)
70
+ return toCallToolResult(recorded, replayContract);
71
+ }
72
+ return toCallToolResult(result, replayContract, {
73
+ evidenceResourcesAvailable: output.evidence !== null && options.recordEvidence !== undefined,
74
+ });
75
+ });
76
+ };
77
+ const recordSources = (recordEvidence, sources) => {
78
+ for (const source of sources) {
79
+ const recorded = recordEvidence?.(source);
80
+ if (recorded !== undefined && !recorded.ok)
81
+ return recorded;
82
+ }
83
+ return { ok: true, value: null };
84
+ };
85
+ const recordResult = (options, contract, evidence, recordUnknown = false) => {
86
+ const recorded = recordUnknown
87
+ ? options.recordEvidenceWithUnknown?.(evidence, {
88
+ approved: true,
89
+ question: "Which application entities remain unmatched or ambiguous across these versions?",
90
+ severity: "medium",
91
+ domain: "application-version-comparison",
92
+ supporting_evidence_ids: [evidence.evidence_id],
93
+ contradicting_evidence_ids: [],
94
+ required_authority: "shipped-artifact",
95
+ required_confidence: "observed",
96
+ required_environment: null,
97
+ recommended_probes: [
98
+ {
99
+ operation: "analyze_javascript_application",
100
+ rationale: "Repeat static reconstruction with complete artifacts and source maps when available.",
101
+ },
102
+ {
103
+ operation: "reconcile_javascript_runtime",
104
+ rationale: "Add approved passive runtime Evidence without promoting it to static fact.",
105
+ },
106
+ ],
107
+ relationships: [],
108
+ })
109
+ : options.recordEvidence?.(evidence);
110
+ if (recorded !== undefined && !recorded.ok)
111
+ return toCallToolResult(recorded, contract);
112
+ return toCallToolResult({ ok: true, value: evidence }, contract, {
113
+ evidenceResourcesAvailable: recorded !== undefined,
114
+ });
115
+ };
@@ -2,27 +2,35 @@ import { artifactComparisonInputSchema, compareArtifacts, } from "../domain/arti
2
2
  import { createEvidence } from "../domain/evidence.js";
3
3
  import { jsonValueSchema } from "../domain/jsonValue.js";
4
4
  import { recordDerivedEvidence } from "./recordDerivedEvidence.js";
5
- import { resolveSessionEvidence } from "./sessionEvidence.js";
5
+ import { resolveSessionEvidenceIds } from "./sessionEvidence.js";
6
6
  import { ARTIFACT_COMPARISON_PROVIDER } from "./sessionToolPolicies.js";
7
7
  import { toCallToolResult } from "./toolResult.js";
8
8
  import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
9
9
  import { runDerivedOperation } from "./runDerivedOperation.js";
10
+ import { safeParseToolInput } from "./toolInputValidation.js";
10
11
  /** Register Evidence-backed deterministic artifact comparison. */
11
12
  export const registerArtifactComparisonTool = (server, session, contract) => {
12
13
  server.registerTool(contract.name, toolRegistrationOptions(contract), async (input, context) => {
13
- const parsed = artifactComparisonInputSchema.parse(input);
14
- const left = resolveSessionEvidence(session, parsed.left);
14
+ const parsedInput = safeParseToolInput(artifactComparisonInputSchema, input, contract.name);
15
+ if (!parsedInput.ok)
16
+ return toCallToolResult(parsedInput, contract);
17
+ const parsed = parsedInput.value;
18
+ const expected = {
19
+ operation: "inventory_artifact",
20
+ predicate: "rea.analysis/v2",
21
+ };
22
+ const left = resolveSessionEvidenceIds(session, parsed.left_evidence_ids, expected);
15
23
  if (!left.ok)
16
24
  return toCallToolResult(left, contract);
17
- const right = resolveSessionEvidence(session, parsed.right);
25
+ const right = resolveSessionEvidenceIds(session, parsed.right_evidence_ids, expected);
18
26
  if (!right.ok)
19
27
  return toCallToolResult(right, contract);
20
28
  const computed = await runDerivedOperation(context, contract.name, () => compareArtifacts(left.value, right.value, parsed.offset, parsed.limit));
21
29
  if (!computed.ok)
22
30
  return toCallToolResult(computed, contract);
23
31
  const comparison = computed.value;
24
- const leftEvidenceIds = evidenceIds(parsed.left);
25
- const rightEvidenceIds = evidenceIds(parsed.right);
32
+ const leftEvidenceIds = parsed.left_evidence_ids;
33
+ const rightEvidenceIds = parsed.right_evidence_ids;
26
34
  const evidence = createEvidence(undefined, ARTIFACT_COMPARISON_PROVIDER, {
27
35
  predicateType: "rea.artifact-comparison/v1",
28
36
  operation: contract.name,
@@ -49,8 +57,8 @@ const artifactUnknownInput = (input, status) => {
49
57
  question: `Artifact comparison is ${status}`,
50
58
  severity: status === "unknown" || status === "truncated" ? "high" : "medium",
51
59
  domain: "artifact-comparison",
52
- supporting_evidence_ids: evidenceIds(input.left),
53
- contradicting_evidence_ids: evidenceIds(input.right),
60
+ supporting_evidence_ids: [...input.left_evidence_ids],
61
+ contradicting_evidence_ids: [...input.right_evidence_ids],
54
62
  required_authority: "shipped-artifact",
55
63
  required_confidence: "observed",
56
64
  required_environment: null,
@@ -63,4 +71,3 @@ const artifactUnknownInput = (input, status) => {
63
71
  relationships: [],
64
72
  };
65
73
  };
66
- const evidenceIds = (input) => (Array.isArray(input) ? input : [input]).map(({ evidence_id: id }) => id);
@@ -9,19 +9,26 @@ import { captureWebScreenshotInputSchema, compareWebScreenshotsInputSchema, } fr
9
9
  import { logToolExecution } from "./toolLogging.js";
10
10
  import { toCallToolResult } from "./toolResult.js";
11
11
  import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
12
+ import { safeParseToolInput } from "./toolInputValidation.js";
12
13
  import { mcpProgressReporter } from "./mcpProgress.js";
13
14
  /** Register browser tools even when policy/provider availability denies execution. */
14
15
  export const registerBrowserTools = (server, options) => {
15
16
  const [listContract, inspectContract, analyzeContract, sessionContract, webMcpContract, captureDiffContract, screenshotContract, screenshotDiffContract,] = BROWSER_TOOL_CONTRACTS;
16
17
  server.registerTool(listContract.name, toolRegistrationOptions(listContract), async (input, context) => {
17
- const parsed = listBrowserTargetsInputSchema.parse(input);
18
+ const parsedInput = safeParseToolInput(listBrowserTargetsInputSchema, input, listContract.name);
19
+ if (!parsedInput.ok)
20
+ return toCallToolResult(parsedInput, listContract);
21
+ const parsed = parsedInput.value;
18
22
  const result = await logToolExecution(options.logger, listContract.name, () => listBrowserTargets(options.browser, options.permissionAuthority, parsed, { signal: context.mcpReq.signal }));
19
23
  if (!result.ok)
20
24
  return toCallToolResult(result, listContract);
21
25
  return evidenceResult(options, listContract, result.value);
22
26
  });
23
27
  server.registerTool(inspectContract.name, toolRegistrationOptions(inspectContract), async (input, context) => {
24
- const parsed = inspectWebPageInputSchema.parse(input);
28
+ const parsedInput = safeParseToolInput(inspectWebPageInputSchema, input, inspectContract.name);
29
+ if (!parsedInput.ok)
30
+ return toCallToolResult(parsedInput, inspectContract);
31
+ const parsed = parsedInput.value;
25
32
  const result = await logToolExecution(options.logger, inspectContract.name, () => inspectWebPage(options.browser, options.permissionAuthority, parsed, {
26
33
  signal: context.mcpReq.signal,
27
34
  progress: mcpProgressReporter(context),
@@ -31,7 +38,10 @@ export const registerBrowserTools = (server, options) => {
31
38
  return evidenceResult(options, inspectContract, result.value);
32
39
  });
33
40
  server.registerTool(analyzeContract.name, toolRegistrationOptions(analyzeContract), async (input, context) => {
34
- const parsed = analyzeWebBundleInputSchema.parse(input);
41
+ const parsedInput = safeParseToolInput(analyzeWebBundleInputSchema, input, analyzeContract.name);
42
+ if (!parsedInput.ok)
43
+ return toCallToolResult(parsedInput, analyzeContract);
44
+ const parsed = parsedInput.value;
35
45
  const result = await logToolExecution(options.logger, analyzeContract.name, () => analyzeWebBundle(options.browser, options.permissionAuthority, parsed, {
36
46
  signal: context.mcpReq.signal,
37
47
  progress: mcpProgressReporter(context),
@@ -41,7 +51,10 @@ export const registerBrowserTools = (server, options) => {
41
51
  return evidenceResult(options, analyzeContract, result.value);
42
52
  });
43
53
  server.registerTool(sessionContract.name, toolRegistrationOptions(sessionContract), async (input, context) => {
44
- const parsed = observeWebSessionInputSchema.parse(input);
54
+ const parsedInput = safeParseToolInput(observeWebSessionInputSchema, input, sessionContract.name);
55
+ if (!parsedInput.ok)
56
+ return toCallToolResult(parsedInput, sessionContract);
57
+ const parsed = parsedInput.value;
45
58
  const result = await logToolExecution(options.logger, sessionContract.name, () => observeWebSession(options.browser, options.permissionAuthority, parsed, {
46
59
  signal: context.mcpReq.signal,
47
60
  progress: mcpProgressReporter(context),
@@ -51,7 +64,10 @@ export const registerBrowserTools = (server, options) => {
51
64
  return evidenceResult(options, sessionContract, result.value);
52
65
  });
53
66
  server.registerTool(webMcpContract.name, toolRegistrationOptions(webMcpContract), async (input, context) => {
54
- const parsed = discoverWebMcpToolsInputSchema.parse(input);
67
+ const parsedInput = safeParseToolInput(discoverWebMcpToolsInputSchema, input, webMcpContract.name);
68
+ if (!parsedInput.ok)
69
+ return toCallToolResult(parsedInput, webMcpContract);
70
+ const parsed = parsedInput.value;
55
71
  const result = await logToolExecution(options.logger, webMcpContract.name, () => discoverWebMcpTools(options.browser, options.permissionAuthority, parsed, {
56
72
  signal: context.mcpReq.signal,
57
73
  progress: mcpProgressReporter(context),
@@ -61,14 +77,20 @@ export const registerBrowserTools = (server, options) => {
61
77
  return evidenceResult(options, webMcpContract, result.value);
62
78
  });
63
79
  server.registerTool(captureDiffContract.name, toolRegistrationOptions(captureDiffContract), async (input) => {
64
- const parsed = compareWebCapturesInputSchema.parse(input);
80
+ const parsedInput = safeParseToolInput(compareWebCapturesInputSchema, input, captureDiffContract.name);
81
+ if (!parsedInput.ok)
82
+ return toCallToolResult(parsedInput, captureDiffContract);
83
+ const parsed = parsedInput.value;
65
84
  const result = await logToolExecution(options.logger, captureDiffContract.name, () => compareWebCaptureEvidence(options.browser, parsed));
66
85
  if (!result.ok)
67
86
  return toCallToolResult(result, captureDiffContract);
68
87
  return evidenceResult(options, captureDiffContract, result.value);
69
88
  });
70
89
  server.registerTool(screenshotContract.name, toolRegistrationOptions(screenshotContract), async (input, context) => {
71
- const parsed = captureWebScreenshotInputSchema.parse(input);
90
+ const parsedInput = safeParseToolInput(captureWebScreenshotInputSchema, input, screenshotContract.name);
91
+ if (!parsedInput.ok)
92
+ return toCallToolResult(parsedInput, screenshotContract);
93
+ const parsed = parsedInput.value;
72
94
  const result = await logToolExecution(options.logger, screenshotContract.name, () => captureWebScreenshot(options.browser, options.permissionAuthority, parsed, {
73
95
  signal: context.mcpReq.signal,
74
96
  progress: mcpProgressReporter(context),
@@ -78,7 +100,10 @@ export const registerBrowserTools = (server, options) => {
78
100
  return evidenceResult(options, screenshotContract, result.value);
79
101
  });
80
102
  server.registerTool(screenshotDiffContract.name, toolRegistrationOptions(screenshotDiffContract), async (input) => {
81
- const parsed = compareWebScreenshotsInputSchema.parse(input);
103
+ const parsedInput = safeParseToolInput(compareWebScreenshotsInputSchema, input, screenshotDiffContract.name);
104
+ if (!parsedInput.ok)
105
+ return toCallToolResult(parsedInput, screenshotDiffContract);
106
+ const parsed = parsedInput.value;
82
107
  const result = await logToolExecution(options.logger, screenshotDiffContract.name, () => compareWebScreenshotEvidence(options.browser, parsed));
83
108
  if (!result.ok)
84
109
  return toCallToolResult(result, screenshotDiffContract);
@@ -1,31 +1,29 @@
1
+ import { readEvidenceBundle } from "../application/EvidenceBundleFiles.js";
1
2
  import { bundleComparisonInputSchema, compareBundles, } from "../domain/bundleComparison.js";
2
3
  import { createEvidence } from "../domain/evidence.js";
3
- import { EvidenceIntegrityError } from "../domain/errors.js";
4
4
  import { jsonValueSchema } from "../domain/jsonValue.js";
5
- import { err, ok } from "../domain/result.js";
5
+ import { ok } from "../domain/result.js";
6
6
  import { BUNDLE_COMPARISON_PROVIDER } from "./sessionToolPolicies.js";
7
7
  import { toCallToolResult } from "./toolResult.js";
8
- import { isSessionEvidence } from "./sessionEvidence.js";
9
8
  import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
10
9
  import { runDerivedOperation } from "./runDerivedOperation.js";
10
+ import { safeParseToolInput } from "./toolInputValidation.js";
11
11
  /** Register canonical Evidence bundle comparison. */
12
- export const registerBundleComparisonTool = (server, session, contract) => {
12
+ export const registerBundleComparisonTool = (server, session, contract, evidenceFilePolicy) => {
13
13
  server.registerTool(contract.name, toolRegistrationOptions(contract), async (input, context) => {
14
- const parsed = bundleComparisonInputSchema.parse(input);
15
- const sourceIds = [
16
- ...new Set([
17
- ...parsed.left.records.map(({ evidence_id: id }) => id),
18
- ...parsed.right.records.map(({ evidence_id: id }) => id),
19
- ]),
20
- ].sort();
21
- if ([...parsed.left.records, ...parsed.right.records].some((evidence) => !isSessionEvidence(session, evidence)))
22
- return toCallToolResult(err(new EvidenceIntegrityError("Bundle comparison input Evidence is not present in this session")), contract);
23
- const ownedUnknownDigests = new Set(session
24
- .exportEvidenceBundle()
25
- .unknowns.map(({ revision_digest: digest }) => digest));
26
- if ([...parsed.left.unknowns, ...parsed.right.unknowns].some(({ revision_digest: digest }) => !ownedUnknownDigests.has(digest)))
27
- return toCallToolResult(err(new EvidenceIntegrityError("Bundle comparison input unknown history is not present in this session")), contract);
28
- const computed = await runDerivedOperation(context, contract.name, () => compareBundles(parsed.left, parsed.right, parsed.record_pairs, parsed.offset, parsed.limit));
14
+ const parsedInput = safeParseToolInput(bundleComparisonInputSchema, input, contract.name);
15
+ if (!parsedInput.ok)
16
+ return toCallToolResult(parsedInput, contract);
17
+ const parsed = parsedInput.value;
18
+ const [left, right] = await Promise.all([
19
+ readEvidenceBundle(parsed.left_bundle_path, evidenceFilePolicy),
20
+ readEvidenceBundle(parsed.right_bundle_path, evidenceFilePolicy),
21
+ ]);
22
+ if (!left.ok)
23
+ return toCallToolResult(left, contract);
24
+ if (!right.ok)
25
+ return toCallToolResult(right, contract);
26
+ const computed = await runDerivedOperation(context, contract.name, () => compareBundles(left.value, right.value, parsed.record_pairs, parsed.offset, parsed.limit));
29
27
  if (!computed.ok)
30
28
  return toCallToolResult(computed, contract);
31
29
  const comparison = computed.value;
@@ -43,7 +41,6 @@ export const registerBundleComparisonTool = (server, session, contract) => {
43
41
  confidence: "derived",
44
42
  authority: "analyst-inference",
45
43
  limitations: comparison.limitations,
46
- evidenceLinks: sourceIds,
47
44
  });
48
45
  const recorded = session.recordEvidence(evidence);
49
46
  return toCallToolResult(recorded.ok ? ok(evidence) : recorded, contract);