rea-agents 6.2.0 → 6.3.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 (297) hide show
  1. package/README.md +18 -20
  2. package/bridge/hopper_bridge.py +2 -0
  3. package/dist/android/JadxProvider.js +8 -2
  4. package/dist/android/JadxSession.js +51 -9
  5. package/dist/application/BinaryTargetResolver.js +63 -17
  6. package/dist/application/BrowserScenarioEvidence.js +2 -2
  7. package/dist/application/ClientConfigurationDocument.js +28 -0
  8. package/dist/application/ClientRegistrationStatus.js +19 -6
  9. package/dist/application/DirectAnalysis.js +12 -6
  10. package/dist/application/DirectAnalysisCleanup.js +0 -1
  11. package/dist/application/DirectAnalysisStatus.js +2 -5
  12. package/dist/application/Doctor.js +25 -34
  13. package/dist/application/DoctorDiagnostics.js +1 -1
  14. package/dist/application/EvidenceBundleFiles.js +1 -1
  15. package/dist/application/InvestigationProviders.js +4 -4
  16. package/dist/application/JsonFiles.js +15 -15
  17. package/dist/application/LinuxHopper.js +2 -1
  18. package/dist/application/ReconstructionObligationCandidates.js +9 -2
  19. package/dist/application/ReconstructionObligationLedgerProjection.js +3 -1
  20. package/dist/application/RegularFileRead.js +17 -0
  21. package/dist/application/Setup.js +18 -7
  22. package/dist/application/SetupClientConfiguration.js +18 -7
  23. package/dist/application/SetupHost.js +6 -4
  24. package/dist/application/SetupPlan.js +19 -15
  25. package/dist/application/SetupSkill.js +57 -8
  26. package/dist/application/SupportedClients.js +43 -0
  27. package/dist/application/Uninstall.js +18 -2
  28. package/dist/application/UpdateMaintenance.js +6 -2
  29. package/dist/application/Utf8JsonInput.js +64 -0
  30. package/dist/application/WebScriptExportService.js +13 -6
  31. package/dist/application/android/AndroidAnalysisService.js +1 -1
  32. package/dist/application/binary/AnalysisSnapshotCache.js +5 -4
  33. package/dist/application/binary/BinarySession.js +16 -0
  34. package/dist/application/binary/BinarySessionOpen.js +9 -1
  35. package/dist/application/javascript/ElectronActiveEvidence.js +2 -2
  36. package/dist/application/javascript/ElectronBoundaryAnalysis.js +2 -2
  37. package/dist/application/javascript/ElectronBoundaryGraphNative.js +5 -1
  38. package/dist/application/javascript/JavaScriptApplicationEvidence.js +1 -0
  39. package/dist/application/javascript/JavaScriptApplicationEvidenceGraph.js +107 -46
  40. package/dist/application/javascript/JavaScriptApplicationService.js +1 -0
  41. package/dist/application/javascript/JavaScriptApplicationWorkflowService.js +59 -45
  42. package/dist/application/javascript/JavaScriptArtifactAnalysis.js +0 -6
  43. package/dist/application/javascript/JavaScriptArtifactGraphBuilder.js +8 -18
  44. package/dist/application/javascript/JavaScriptArtifactGraphFindings.js +34 -26
  45. package/dist/application/javascript/JavaScriptArtifactPathResolution.js +44 -12
  46. package/dist/application/javascript/JavaScriptArtifactReconstruction.js +6 -3
  47. package/dist/application/javascript/JavaScriptArtifactReconstructionInput.js +1 -0
  48. package/dist/application/javascript/JavaScriptJsonModules.js +2 -2
  49. package/dist/application/javascript/JavaScriptModuleRelationships.js +5 -17
  50. package/dist/application/javascript/JavaScriptRuntimeReconciliationService.js +11 -27
  51. package/dist/application/javascript/JavaScriptSemanticGraphAsyncProjection.js +2 -2
  52. package/dist/application/javascript/JavaScriptSemanticGraphBuilder.js +8 -6
  53. package/dist/application/javascript/JavaScriptSemanticGraphChildProcessProjection.js +1 -1
  54. package/dist/application/javascript/JavaScriptSemanticGraphConstruction.js +7 -5
  55. package/dist/application/javascript/JavaScriptSemanticGraphDataProjection.js +1 -1
  56. package/dist/application/javascript/JavaScriptSemanticGraphEvidence.js +1 -1
  57. package/dist/application/javascript/JavaScriptSemanticGraphFingerprintProjection.js +2 -2
  58. package/dist/application/javascript/JavaScriptSemanticGraphFlowProjection.js +2 -2
  59. package/dist/application/javascript/JavaScriptSemanticGraphObjectProjection.js +1 -1
  60. package/dist/application/javascript/JavaScriptSemanticGraphProjection.js +4 -4
  61. package/dist/application/javascript/JavaScriptSemanticGraphResourceProjection.js +1 -1
  62. package/dist/application/javascript/JavaScriptSemanticGraphValueProjection.js +10 -7
  63. package/dist/application/javascript/JavaScriptSemanticTraceService.js +15 -10
  64. package/dist/application/native/NativeCallRoutes.js +2 -4
  65. package/dist/artifacts/ArtifactProvider.js +14 -16
  66. package/dist/artifacts/ArtifactProviderMetadata.js +1 -4
  67. package/dist/artifacts/AsarArtifactReader.js +81 -17
  68. package/dist/artifacts/DirectoryArtifactReader.js +2 -2
  69. package/dist/artifacts/NativeDmgArtifactReader.js +152 -67
  70. package/dist/artifacts/NativeDmgOwnership.js +109 -0
  71. package/dist/artifacts/apple/KeyedArchiveReader.js +54 -46
  72. package/dist/artifacts/apple/MachoLoadCommandReader.js +36 -6
  73. package/dist/artifacts/extraction/ArtifactExtraction.js +29 -11
  74. package/dist/artifacts/inventory/ArtifactGraphConstruction.js +7 -8
  75. package/dist/artifacts/inventory/classify.js +33 -14
  76. package/dist/artifacts/inventory/hashStableRootArtifact.js +37 -0
  77. package/dist/artifacts/inventory/scanCanonical.js +28 -22
  78. package/dist/artifacts/inventory/scanReader.js +7 -11
  79. package/dist/artifacts/javascript/JavaScriptArtifactFiles.js +65 -27
  80. package/dist/browser/CdpBrowserProvider.js +4 -3
  81. package/dist/browser/CdpCaptureStorageFingerprints.js +3 -3
  82. package/dist/browser/CdpElectronInspection.js +2 -2
  83. package/dist/browser/CdpElectronProvider.js +4 -3
  84. package/dist/browser/CdpElectronScripts.js +2 -2
  85. package/dist/browser/CdpWebMcpDiscovery.js +16 -13
  86. package/dist/browser/PlaywrightBrowserScenarioProvider.js +6 -6
  87. package/dist/browser/PlaywrightScenarioBrowser.js +145 -42
  88. package/dist/browser/PlaywrightScenarioSession.js +3 -11
  89. package/dist/browser/WebSourceMapFetcher.js +32 -6
  90. package/dist/browser/assets/ScenarioScriptCapture.js +8 -12
  91. package/dist/cli/coreAnalysisCommands.js +2 -2
  92. package/dist/cli/coreBinaryCommands.js +13 -9
  93. package/dist/cli/electronCommands.js +9 -10
  94. package/dist/cli/javascriptApplicationAnalysis.js +2 -12
  95. package/dist/cli/jsonOutput.js +3 -2
  96. package/dist/cli/streamedJsonOutput.js +83 -4
  97. package/dist/cli/utilityCommands.js +2 -4
  98. package/dist/cli.js +5 -2
  99. package/dist/cliJsonFile.js +179 -0
  100. package/dist/cliJsonInput.js +47 -34
  101. package/dist/cliObservationOptions.js +4 -0
  102. package/dist/composition/auxiliaryAnalysisProviders.js +5 -4
  103. package/dist/composition/browserScenario.js +2 -2
  104. package/dist/composition/directAnalysis.js +2 -3
  105. package/dist/contracts/artifactComparisonExample.js +5 -5
  106. package/dist/contracts/errorSchemas.js +3 -1
  107. package/dist/contracts/functionComparisonExample.js +8 -0
  108. package/dist/contracts/inputSchemaPresentation.js +94 -3
  109. package/dist/contracts/javascript/electronToolContracts.js +2 -1
  110. package/dist/contracts/javascript/javascriptApplicationWorkflowExamples.js +3 -2
  111. package/dist/contracts/javascript/javascriptExportShapeComparisonExample.js +6 -3
  112. package/dist/contracts/javascript/javascriptRuntimeReconciliationExample.js +8 -7
  113. package/dist/contracts/managed/managedToolContracts.js +3 -2
  114. package/dist/contracts/mobileApplicationGraphExamples.js +5 -5
  115. package/dist/contracts/native/nativeToolContracts.js +1 -1
  116. package/dist/contracts/sessionLifecycleInputs.js +4 -3
  117. package/dist/contracts/sessionStatusContract.js +3 -2
  118. package/dist/contracts/sessionToolContracts.js +3 -2
  119. package/dist/contracts/sessionToolSchemas.js +3 -2
  120. package/dist/contracts/toolOutputSchemaGroups.js +2 -2
  121. package/dist/contracts/toolOutputSchemaPrimitives.js +2 -5
  122. package/dist/domain/analysisErrorBase.js +5 -0
  123. package/dist/domain/analysisErrorPresentation.js +4 -2
  124. package/dist/domain/analysisSnapshot.js +3 -1
  125. package/dist/domain/analysisView/analysisView.js +1 -3
  126. package/dist/domain/android/androidPartialObservation.js +36 -0
  127. package/dist/domain/apple/dylibResolution.js +1 -1
  128. package/dist/domain/artifactComparison.js +4 -4
  129. package/dist/domain/artifactGraph.js +1 -1
  130. package/dist/domain/artifactIdentity.js +8 -8
  131. package/dist/domain/artifactInspection.js +8 -8
  132. package/dist/domain/artifactInventoryEvidence.js +5 -5
  133. package/dist/domain/browserCompleteness.js +3 -2
  134. package/dist/domain/browserScenarioCaptureValues.js +2 -2
  135. package/dist/domain/browserScenarioNormalization.js +3 -3
  136. package/dist/domain/browserScenarioValues.js +3 -2
  137. package/dist/domain/callPath.js +8 -10
  138. package/dist/domain/canonicalDigest.js +7 -1
  139. package/dist/domain/canonicalOrdering.js +1 -13
  140. package/dist/domain/changedBehavior.js +2 -3
  141. package/dist/domain/comparisonSemantics.js +0 -5
  142. package/dist/domain/digests.js +0 -3
  143. package/dist/domain/evidence.js +10 -5
  144. package/dist/domain/evidenceBundle.js +51 -33
  145. package/dist/domain/firmware/firmwareAnalysis.js +4 -3
  146. package/dist/domain/functionComparison.js +2 -1
  147. package/dist/domain/functionComparisonNormalization.js +4 -3
  148. package/dist/domain/functionComparisonResults.js +2 -2
  149. package/dist/domain/functionDossierEvidence.js +2 -1
  150. package/dist/domain/hopperValues.js +11 -15
  151. package/dist/domain/javascript/electronActiveObservation.js +4 -3
  152. package/dist/domain/javascript/electronStaticAnalysisBrowser.js +2 -2
  153. package/dist/domain/javascript/electronStaticAnalysisIpc.js +9 -1
  154. package/dist/domain/javascript/electronStaticAnalysisNative.js +5 -3
  155. package/dist/domain/javascript/electronStaticAnalysisValues.js +8 -4
  156. package/dist/domain/javascript/javascriptApplicationAnalysis.js +3 -1
  157. package/dist/domain/javascript/javascriptApplicationChangeGraph.js +12 -8
  158. package/dist/domain/javascript/javascriptApplicationEvidenceSchemas.js +4 -0
  159. package/dist/domain/javascript/javascriptApplicationGraph.js +15 -13
  160. package/dist/domain/javascript/javascriptApplicationVersionComparison.js +4 -4
  161. package/dist/domain/javascript/javascriptApplicationVersionComparisonSchemas.js +37 -22
  162. package/dist/domain/javascript/javascriptApplicationVersionItems.js +45 -34
  163. package/dist/domain/javascript/javascriptApplicationVersionKeys.js +7 -7
  164. package/dist/domain/javascript/javascriptAstFingerprint.js +2 -2
  165. package/dist/domain/javascript/javascriptAstValues.js +22 -0
  166. package/dist/domain/javascript/javascriptExportShapeComparison.js +3 -2
  167. package/dist/domain/javascript/javascriptExportShapeComparisonSchemas.js +31 -23
  168. package/dist/domain/javascript/javascriptExportShapeSelection.js +2 -2
  169. package/dist/domain/javascript/javascriptExportShapeVariants.js +3 -2
  170. package/dist/domain/javascript/javascriptFeatureSeed.js +5 -3
  171. package/dist/domain/javascript/javascriptFeatureTrace.js +7 -6
  172. package/dist/domain/javascript/javascriptFeatureTraceSchemas.js +17 -9
  173. package/dist/domain/javascript/javascriptFeatureTraversal.js +4 -4
  174. package/dist/domain/javascript/javascriptNativeHandoff.js +6 -6
  175. package/dist/domain/javascript/javascriptRuntimeObservation.js +8 -5
  176. package/dist/domain/javascript/javascriptRuntimeReconciliation.js +12 -6
  177. package/dist/domain/javascript/javascriptRuntimeReconciliationActive.js +9 -8
  178. package/dist/domain/javascript/javascriptRuntimeReconciliationCaptureParsing.js +212 -0
  179. package/dist/domain/javascript/javascriptRuntimeReconciliationGraph.js +5 -4
  180. package/dist/domain/javascript/javascriptRuntimeReconciliationInputValidation.js +28 -0
  181. package/dist/domain/javascript/javascriptRuntimeReconciliationMatching.js +5 -5
  182. package/dist/domain/javascript/javascriptRuntimeReconciliationParsing.js +12 -223
  183. package/dist/domain/javascript/javascriptRuntimeReconciliationResult.js +2 -2
  184. package/dist/domain/javascript/javascriptRuntimeReconciliationRuntime.js +15 -4
  185. package/dist/domain/javascript/javascriptRuntimeReconciliationSchemas.js +2 -2
  186. package/dist/domain/javascript/javascriptRuntimeStaticCandidates.js +7 -6
  187. package/dist/domain/javascript/javascriptSemanticAnalysis.js +200 -8
  188. package/dist/domain/javascript/javascriptSemanticCallResolution.js +9 -7
  189. package/dist/domain/javascript/javascriptSemanticChildProcesses.js +2 -2
  190. package/dist/domain/javascript/javascriptSemanticCoverage.js +20 -0
  191. package/dist/domain/javascript/javascriptSemanticDataEffectHelpers.js +7 -9
  192. package/dist/domain/javascript/javascriptSemanticDataEffects.js +4 -11
  193. package/dist/domain/javascript/javascriptSemanticFingerprints.js +2 -2
  194. package/dist/domain/javascript/javascriptSemanticGraph.js +142 -35
  195. package/dist/domain/javascript/javascriptSemanticGraphSchemas.js +19 -1
  196. package/dist/domain/javascript/javascriptSemanticIr.js +0 -4
  197. package/dist/domain/javascript/javascriptSemanticIterationSources.js +106 -0
  198. package/dist/domain/javascript/javascriptSemanticMemberMutations.js +497 -113
  199. package/dist/domain/javascript/javascriptSemanticMutationInitializers.js +190 -0
  200. package/dist/domain/javascript/javascriptSemanticMutationValues.js +41 -27
  201. package/dist/domain/javascript/javascriptSemanticObjects.js +2 -6
  202. package/dist/domain/javascript/javascriptSemanticPrimitives.js +7 -2
  203. package/dist/domain/javascript/javascriptSemanticProjection.js +20 -14
  204. package/dist/domain/javascript/javascriptSemanticPromises.js +15 -21
  205. package/dist/domain/javascript/javascriptSemanticPropertyPaths.js +23 -0
  206. package/dist/domain/javascript/javascriptSemanticProvenance.js +3 -3
  207. package/dist/domain/javascript/javascriptSemanticQuery.js +8 -2
  208. package/dist/domain/javascript/javascriptSemanticQueryIdentity.js +3 -2
  209. package/dist/domain/javascript/javascriptSemanticQuerySchemas.js +8 -9
  210. package/dist/domain/javascript/javascriptSemanticRequireOrigin.js +16 -10
  211. package/dist/domain/javascript/javascriptSemanticReturns.js +23 -27
  212. package/dist/domain/javascript/javascriptSemanticTraversal.js +6 -1
  213. package/dist/domain/javascript/javascriptSemanticValues.js +135 -24
  214. package/dist/domain/javascript/javascriptSourceMapPaths.js +11 -1
  215. package/dist/domain/javascript/javascriptSourceParser.js +34 -9
  216. package/dist/domain/javascript/javascriptStaticAnalysisBundler.js +5 -6
  217. package/dist/domain/javascript/javascriptStaticAnalysisHelpers.js +26 -15
  218. package/dist/domain/javascript/sourceToBundleComparison.js +7 -5
  219. package/dist/domain/javascript/sourceToBundleSignals.js +7 -7
  220. package/dist/domain/jsonSerialization.js +109 -55
  221. package/dist/domain/jsonShape.js +46 -19
  222. package/dist/domain/localPath.js +10 -4
  223. package/dist/domain/managed/managedApplicationGraph.js +10 -6
  224. package/dist/domain/managed/managedApplicationGraphNodes.js +82 -57
  225. package/dist/domain/managed/managedMemberComparisonMatch.js +6 -7
  226. package/dist/domain/native/nativeCallObservation.js +2 -4
  227. package/dist/domain/native/nativeValueFlow.js +5 -11
  228. package/dist/domain/native/objcSwiftMetadata.js +15 -17
  229. package/dist/domain/process/processCapture.js +11 -10
  230. package/dist/domain/process/processCaptureCoverage.js +8 -5
  231. package/dist/domain/process/processCaptureExample.js +43 -0
  232. package/dist/domain/process/processCaptureValidation.js +4 -3
  233. package/dist/domain/process/processComparison.js +0 -23
  234. package/dist/domain/process/processScenario.js +3 -3
  235. package/dist/domain/process/processTraceEvaluation.js +4 -4
  236. package/dist/domain/process/processTraceSpecification.js +1 -3
  237. package/dist/domain/providerCleanupError.js +6 -2
  238. package/dist/domain/reconstructionCoverage.js +2 -1
  239. package/dist/domain/reconstructionUnknowns.js +4 -3
  240. package/dist/domain/reconstructionVerification.js +3 -2
  241. package/dist/domain/referenceSourceGraph.js +7 -7
  242. package/dist/domain/referenceSourceImportParsing.js +34 -45
  243. package/dist/domain/residualUnknown.js +3 -2
  244. package/dist/domain/staticRuntimeCorrelation.js +2 -1
  245. package/dist/domain/swiftSymbolClassification.js +6 -1
  246. package/dist/domain/symbolAnalysis.js +17 -2
  247. package/dist/domain/unicodeCodePointOrder.js +6 -0
  248. package/dist/domain/webBundleAnalyzerInspection.js +9 -1
  249. package/dist/domain/webCaptureDiff.js +71 -29
  250. package/dist/domain/webCaptureDiffSchemas.js +2 -8
  251. package/dist/domain/webInventory.js +3 -2
  252. package/dist/domain/webMcpDiscovery.js +4 -3
  253. package/dist/domain/webScriptExport.js +1 -1
  254. package/dist/dotnet/ManagedMemberInstructionDecoder.js +1 -2
  255. package/dist/dotnet/ManagedStaticProvider.js +10 -7
  256. package/dist/dotnet/ManagedStaticProviderMetadata.js +1 -4
  257. package/dist/filesystem/RegularFile.js +41 -0
  258. package/dist/generatedPackageMetadata.js +1 -1
  259. package/dist/ghidra/GhidraClient.js +14 -4
  260. package/dist/ghidra/GhidraInstallation.js +111 -10
  261. package/dist/ghidra/GhidraLauncher.js +43 -19
  262. package/dist/hopper/HopperCleanup.js +1 -1
  263. package/dist/ida/IdaOperationRunner.js +2 -0
  264. package/dist/ida/IdaSessionClient.js +4 -2
  265. package/dist/inspector/JavaScriptRuntimeScope.js +9 -5
  266. package/dist/inspector/V8InspectorCaptureProjection.js +46 -13
  267. package/dist/inspector/V8InspectorProvider.js +60 -8
  268. package/dist/main/shutdown.js +76 -32
  269. package/dist/main/transport.js +1 -1
  270. package/dist/native/AppleObjcDispatchFacets.js +3 -3
  271. package/dist/native/AppleObjcProperties.js +17 -7
  272. package/dist/native/LldbCallTracer.js +241 -75
  273. package/dist/native/NativeMacOSProvider.js +10 -5
  274. package/dist/native/NativeMacOSProviderMetadata.js +1 -2
  275. package/dist/process/ProcessOwnershipObservation.js +9 -13
  276. package/dist/process/capture/FilesystemSnapshot.js +88 -18
  277. package/dist/process/capture/ProcessCaptureLifecycle.js +13 -27
  278. package/dist/process/capture/ProcessFilesystemEffects.js +19 -4
  279. package/dist/process/capture/ProcessHarness.js +8 -21
  280. package/dist/server/analysisAdmission.js +8 -0
  281. package/dist/server/createServer.js +22 -23
  282. package/dist/server/registerApplicationTools/compareExportShapes.js +2 -1
  283. package/dist/server/registerApplicationTools/helpers.js +3 -2
  284. package/dist/server/registerArtifactTools.js +2 -2
  285. package/dist/server/registerCloseLifecycleTool.js +6 -4
  286. package/dist/server/registerEnhancedTools.js +78 -73
  287. package/dist/server/registerEvidenceTools.js +54 -46
  288. package/dist/server/registerManagedTools.js +7 -4
  289. package/dist/server/registerOfficialTools.js +31 -25
  290. package/dist/server/registerSessionTools.js +9 -3
  291. package/native/windows/build/manifest.json +1 -1
  292. package/package.json +6 -3
  293. package/skills/reverse-engineer-anything/SKILL.md +5 -5
  294. package/skills/reverse-engineer-anything/references/native-and-artifacts.md +9 -0
  295. package/dist/artifacts/inventory/policy.js +0 -2
  296. package/dist/domain/managed/managedApplicationGraphNodeCoverage.js +0 -6
  297. package/dist/server/registerNativeTools.js +0 -6
@@ -1,4 +1,5 @@
1
- import { canonicalDigest } from "./comparisonSemantics.js";
1
+ import { digestCanonicalValue } from "./canonicalDigest.js";
2
+ import { compareUnicodeCodePoints } from "./unicodeCodePointOrder.js";
2
3
  import { webCaptureDiffSchema, } from "./webCaptureDiffSchemas.js";
3
4
  /** Compare normalized observations without treating incomplete absence as proof. */
4
5
  export const compareWebCaptures = (input) => {
@@ -41,8 +42,8 @@ const compareIdentities = (before, after) => {
41
42
  for (const identity of after.keys())
42
43
  if (!before.has(identity))
43
44
  changes.push({ identity, change: "added" });
44
- return changes.sort((left, right) => left.identity.localeCompare(right.identity) ||
45
- left.change.localeCompare(right.change));
45
+ return changes.sort((left, right) => compareUnicodeCodePoints(left.identity, right.identity) ||
46
+ compareUnicodeCodePoints(left.change, right.change));
46
47
  };
47
48
  const compareDimension = (before, after, complete, reason) => {
48
49
  const all = compareIdentities(before, after).filter(({ change }) => complete || change === "modified");
@@ -72,7 +73,7 @@ const accessibilityDimension = (before, after) => {
72
73
  const afterAccess = accessibilityComparable(after);
73
74
  const textComparable = beforeAccess.text && afterAccess.text;
74
75
  const nodesComparable = beforeAccess.nodes && afterAccess.nodes;
75
- return compareDimension(singleton("accessibility_tree", digest(accessibilityProjection(before.accessibility, textComparable, nodesComparable))), singleton("accessibility_tree", digest(accessibilityProjection(after.accessibility, textComparable, nodesComparable))), beforeAccess.complete && afterAccess.complete, "Accessibility tree or text capture was incomplete in at least one observation.");
76
+ return compareDimension(singleton("accessibility_tree", digestCanonicalValue(accessibilityProjection(before.accessibility, textComparable, nodesComparable))), singleton("accessibility_tree", digestCanonicalValue(accessibilityProjection(after.accessibility, textComparable, nodesComparable))), beforeAccess.complete && afterAccess.complete, "Accessibility tree or text capture was incomplete in at least one observation.");
76
77
  };
77
78
  const storageDimension = (before, after) => {
78
79
  const keysComplete = storageKeysComparable(before) && storageKeysComparable(after);
@@ -86,32 +87,65 @@ const storageDimension = (before, after) => {
86
87
  const compareWebCaptureDimensions = (input) => {
87
88
  const before = input.before.inspection;
88
89
  const after = input.after.inspection;
89
- const dimension = compareDimension;
90
90
  return {
91
- dom_structure: dimension(singleton("document", digest(domProjection(before))), singleton("document", digest(domProjection(after))), sectionsComplete(before, ["frames", "dom"]) &&
91
+ dom_structure: compareDimension(singleton("document", digestCanonicalValue(domProjection(before))), singleton("document", digestCanonicalValue(domProjection(after))), sectionsComplete(before, ["frames", "dom"]) &&
92
92
  sectionsComplete(after, ["frames", "dom"]), "DOM or frame capture was incomplete in at least one observation."),
93
- scripts: dimension(keyed(before.scripts.items.map(scriptProjection), (item) => item.script_key), keyed(after.scripts.items.map(scriptProjection), (item) => item.script_key), sectionsComplete(before, ["scripts"]) &&
93
+ scripts: compareDimension(keyed(before.scripts.items.map(scriptProjection), (item) => item.script_key), keyed(after.scripts.items.map(scriptProjection), (item) => item.script_key), sectionsComplete(before, ["scripts"]) &&
94
94
  sectionsComplete(after, ["scripts"]), "Script inventory was incomplete in at least one observation."),
95
- resources: dimension(keyed(before.resources, (item) => item.resource_key), keyed(after.resources, (item) => item.resource_key), sectionsComplete(before, ["resources"]) &&
95
+ resources: compareDimension(keyed(before.resources, (item) => item.resource_key), keyed(after.resources, (item) => item.resource_key), sectionsComplete(before, ["resources"]) &&
96
96
  sectionsComplete(after, ["resources"]), "Resource inventory was incomplete in at least one observation."),
97
- network: dimension(networkMap(before), networkMap(after), sectionsComplete(before, ["network_requests"]) &&
98
- sectionsComplete(after, ["network_requests"]), "Network capture is attach-window limited or incomplete."),
99
- metadata: dimension(singleton("metadata", digest(metadataProjection(before))), singleton("metadata", digest(metadataProjection(after))), sectionsComplete(before, ["metadata"]) &&
97
+ network: networkDimension(before, after),
98
+ metadata: compareDimension(singleton("metadata", digestCanonicalValue(metadataProjection(before))), singleton("metadata", digestCanonicalValue(metadataProjection(after))), sectionsComplete(before, ["metadata"]) &&
100
99
  sectionsComplete(after, ["metadata"]), "Safe metadata capture was incomplete in at least one observation."),
101
- webmcp: dimension(webMcpMap(input.before.webmcp), webMcpMap(input.after.webmcp), webMcpComplete(input.before.webmcp) && webMcpComplete(input.after.webmcp), "WebMCP discovery was unavailable or incomplete in at least one capture."),
100
+ webmcp: compareDimension(webMcpMap(input.before.webmcp), webMcpMap(input.after.webmcp), webMcpComplete(input.before.webmcp) && webMcpComplete(input.after.webmcp), "WebMCP discovery was unavailable or incomplete in at least one capture."),
102
101
  accessibility: accessibilityDimension(before, after),
103
102
  storage: storageDimension(before, after),
104
103
  };
105
104
  };
106
- const keyed = (values, identity) => new Map(values.map((value) => [identity(value), digest(value)]));
107
- const networkMap = (inspection) => {
105
+ const keyed = (values, identity) => new Map(values.map((value) => [identity(value), digestCanonicalValue(value)]));
106
+ const bodyShapesSelected = (inspection) => !inspection.completeness.excluded.some(({ section, reason }) => section === "json_body_shapes" && reason === "not_approved") &&
107
+ (inspection.network.requests.length === 0 ||
108
+ inspection.network.requests.some(({ body_shapes }) => body_shapes.status !== "not_approved"));
109
+ const networkIdentity = (request) => `net_${digestCanonicalValue({ method: request.method, url: request.url, resource_type: request.resource_type })}`;
110
+ const networkDimension = (before, after) => {
111
+ const selected = bodyShapesSelected(before) && bodyShapesSelected(after);
112
+ const sources = new Map();
113
+ if (selected) {
114
+ const observedSources = (inspection) => {
115
+ const result = new Map();
116
+ for (const request of inspection.network.requests) {
117
+ const identity = networkIdentity(request);
118
+ const previous = result.get(identity);
119
+ result.set(identity, {
120
+ request: previous?.request !== false && request.body_shapes.request !== null,
121
+ response: previous?.response !== false &&
122
+ request.body_shapes.response !== null,
123
+ });
124
+ }
125
+ return result;
126
+ };
127
+ const left = observedSources(before);
128
+ const right = observedSources(after);
129
+ for (const [identity, coverage] of left) {
130
+ const next = right.get(identity);
131
+ sources.set(identity, {
132
+ request: coverage.request && next?.request === true,
133
+ response: coverage.response && next?.response === true,
134
+ });
135
+ }
136
+ }
137
+ const shapesComplete = !selected ||
138
+ [before, after].every((inspection) => sectionsComplete(inspection, ["json_body_shapes"]) &&
139
+ inspection.network.requests.every(({ body_shapes }) => body_shapes.status === "included"));
140
+ return compareDimension(networkMap(before, sources), networkMap(after, sources), sectionsComplete(before, ["network_requests"]) &&
141
+ sectionsComplete(after, ["network_requests"]) &&
142
+ shapesComplete, "Network capture or selected JSON body-shape coverage is attach-window limited, unavailable, or incomplete.");
143
+ };
144
+ const networkMap = (inspection, sources) => {
108
145
  const grouped = new Map();
109
146
  for (const request of inspection.network.requests) {
110
- const identity = `net_${digest({
111
- method: request.method,
112
- url: request.url,
113
- resource_type: request.resource_type,
114
- })}`;
147
+ const identity = networkIdentity(request);
148
+ const shapes = sources.get(identity);
115
149
  const values = grouped.get(identity) ?? [];
116
150
  values.push({
117
151
  status: request.status,
@@ -119,13 +153,18 @@ const networkMap = (inspection) => {
119
153
  encoded_data_length: request.encoded_data_length,
120
154
  redirects: (request.redirects ?? []).map(({ request_timestamp: _requestTimestamp, redirect_event_timestamp: _redirectEventTimestamp, ...redirect }) => redirect),
121
155
  initiator: request.initiator,
122
- body_shapes: request.body_shapes,
156
+ ...(shapes?.request === true
157
+ ? { request_shape: request.body_shapes.request }
158
+ : {}),
159
+ ...(shapes?.response === true
160
+ ? { response_shape: request.body_shapes.response }
161
+ : {}),
123
162
  });
124
163
  grouped.set(identity, values);
125
164
  }
126
165
  return new Map([...grouped].map(([identity, values]) => [
127
166
  identity,
128
- digest(values.map((value) => digest(value)).sort()),
167
+ digestCanonicalValue(values.map((value) => digestCanonicalValue(value)).sort()),
129
168
  ]));
130
169
  };
131
170
  const webMcpMap = (discovery) => discovery === null
@@ -147,7 +186,9 @@ const incompleteSections = (completeness) => new Set([
147
186
  const domProjection = (inspection) => ({
148
187
  frames: inspection.frames
149
188
  .map(({ url, origin }) => ({ url, origin }))
150
- .sort((left, right) => left.url.localeCompare(right.url)),
189
+ .sort((left, right) => compareUnicodeCodePoints(left.url, right.url) ||
190
+ compareUnicodeCodePoints(left.origin ?? "", right.origin ?? "") ||
191
+ Number(left.origin !== null) - Number(right.origin !== null)),
151
192
  nodes: inspection.dom.nodes.map(({ index: _index, ...node }) => node),
152
193
  });
153
194
  const scriptProjection = (script) => ({
@@ -162,17 +203,18 @@ const scriptProjection = (script) => ({
162
203
  const metadataProjection = (inspection) => ({
163
204
  responses: inspection.metadata.responses
164
205
  .map(({ request_id: _requestId, ...response }) => response)
165
- .map((value) => digest(value))
206
+ .map((value) => digestCanonicalValue(value))
207
+ .sort(),
208
+ dom_urls: inspection.metadata.dom_urls
209
+ .map((value) => digestCanonicalValue(value))
166
210
  .sort(),
167
- dom_urls: inspection.metadata.dom_urls.map((value) => digest(value)).sort(),
168
211
  agent_hints: inspection.metadata.agent_hints
169
- .map((value) => digest(value))
212
+ .map((value) => digestCanonicalValue(value))
170
213
  .sort(),
171
214
  excluded_dom_urls: inspection.metadata.excluded_dom_urls,
172
215
  headers_allowlisted: inspection.metadata.headers_allowlisted,
173
216
  });
174
217
  const singleton = (key, value) => new Map([[key, value]]);
175
- const digest = (value) => canonicalDigest(value);
176
218
  const accessibilityComparable = (inspection) => {
177
219
  const text = inspection.accessibility.text_capture.status === "included" &&
178
220
  inspection.accessibility.text_capture.excluded_fields === 0;
@@ -209,7 +251,7 @@ const storageMap = (storage, includeKeys, includeUsage, fingerprintIdentities) =
209
251
  const map = new Map([
210
252
  [
211
253
  "storage:summary",
212
- digest({
254
+ digestCanonicalValue({
213
255
  origin: storage.origin,
214
256
  values_redacted: storage.values_redacted,
215
257
  ...(includeUsage
@@ -225,7 +267,7 @@ const storageMap = (storage, includeKeys, includeUsage, fingerprintIdentities) =
225
267
  return map;
226
268
  const add = (kind, keys) => {
227
269
  for (const key of keys) {
228
- map.set(`storage:${kind}:${key}`, digest(key));
270
+ map.set(`storage:${kind}:${key}`, digestCanonicalValue(key));
229
271
  }
230
272
  };
231
273
  add("local_storage", storage.local_storage_keys);
@@ -236,7 +278,7 @@ const storageMap = (storage, includeKeys, includeUsage, fingerprintIdentities) =
236
278
  const identity = `${fingerprint.scope}:${fingerprint.identity_sha256}`;
237
279
  if (!fingerprintIdentities.has(identity))
238
280
  continue;
239
- map.set(`storage:content:${identity}`, digest(fingerprint.value_sha256));
281
+ map.set(`storage:content:${identity}`, digestCanonicalValue(fingerprint.value_sha256));
240
282
  }
241
283
  return map;
242
284
  };
@@ -50,12 +50,6 @@ const dimensionSchema = z.union([
50
50
  reason: z.string(),
51
51
  }),
52
52
  ]);
53
- const legacyUnknownDimension = {
54
- status: "unknown",
55
- total_changes: 0,
56
- changes: [],
57
- reason: "Dimension was not recorded by this version 1 capture diff.",
58
- };
59
53
  /** Completeness-aware changes across stable browser evidence dimensions. */
60
54
  export const webCaptureDiffSchema = z
61
55
  .object({
@@ -69,8 +63,8 @@ export const webCaptureDiffSchema = z
69
63
  network: dimensionSchema,
70
64
  metadata: dimensionSchema,
71
65
  webmcp: dimensionSchema,
72
- accessibility: dimensionSchema.default(legacyUnknownDimension),
73
- storage: dimensionSchema.default(legacyUnknownDimension),
66
+ accessibility: dimensionSchema,
67
+ storage: dimensionSchema,
74
68
  }),
75
69
  limitations: z.array(z.string()),
76
70
  })
@@ -1,5 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import canonicalize from "canonicalize";
3
+ import { compareUnicodeCodePoints } from "./unicodeCodePointOrder.js";
3
4
  /** Assign deterministic identities to a bounded resource inventory. */
4
5
  export const stableWebResources = (resources) => {
5
6
  const keyed = resources.map((resource) => ({
@@ -8,8 +9,8 @@ export const stableWebResources = (resources) => {
8
9
  }));
9
10
  return [
10
11
  ...new Map(keyed.map((resource) => [resource.resource_key, resource])).values(),
11
- ].sort((left, right) => left.resource_key.localeCompare(right.resource_key) ||
12
- left.url.localeCompare(right.url));
12
+ ].sort((left, right) => compareUnicodeCodePoints(left.resource_key, right.resource_key) ||
13
+ compareUnicodeCodePoints(left.url, right.url));
13
14
  };
14
15
  /** Reconcile one script with resource-tree observations without fuzzy matching. */
15
16
  export const reconcileWebScript = (script, resources) => {
@@ -2,8 +2,8 @@ import { z } from "zod";
2
2
  import { browserCompletenessSchema } from "./browserCompleteness.js";
3
3
  import { browserAllowedOriginsSchema, browserEndpointSchema, } from "./browserObservation.js";
4
4
  import { browserVersionSchema } from "./browserObservationSchemas.js";
5
- import { jsonShapeSchema } from "./jsonShape.js";
6
- import { prefixedDigestSchema } from "./../domain/digests.js";
5
+ import { jsonValueSchema } from "./jsonValue.js";
6
+ import { digestSchema, prefixedDigestSchema } from "./../domain/digests.js";
7
7
  /** Input for passive discovery of page-registered WebMCP tools. */
8
8
  export const discoverWebMcpToolsInputSchema = z.strictObject({
9
9
  cdp_endpoint: browserEndpointSchema,
@@ -19,7 +19,8 @@ const webMcpToolSchema = z.object({
19
19
  frame_url: z.string(),
20
20
  owner_origin: z.string(),
21
21
  declaration_kind: z.enum(["declarative", "imperative"]),
22
- input_schema_shape: jsonShapeSchema.nullable(),
22
+ input_schema: jsonValueSchema.nullable(),
23
+ input_schema_sha256: digestSchema.nullable(),
23
24
  annotations: z.object({
24
25
  read_only: z.boolean().nullable(),
25
26
  untrusted_content: z.boolean().nullable(),
@@ -24,7 +24,7 @@ const sourceSchema = z.discriminatedUnion("kind", [
24
24
  }),
25
25
  z.strictObject({
26
26
  kind: z.literal("scenario-response"),
27
- transaction_id: z.string().nullable(),
27
+ transaction_id: z.string().min(1),
28
28
  request_sequence: z.number().int().min(1),
29
29
  response_sequence: z.number().int().min(1).nullable(),
30
30
  status: z.number().nullable(),
@@ -1,4 +1,3 @@
1
- const opcode = (code) => OPCODES.get(code);
2
1
  const PREFIX_OPCODES = new Set([
3
2
  "unaligned.",
4
3
  "volatile.",
@@ -361,7 +360,7 @@ export const decodeInstructions = (il) => {
361
360
  const first = il.readUInt8(offset);
362
361
  offset += 1;
363
362
  const code = first === 0xfe ? 0xfe00 + il.readUInt8(offset++) : first;
364
- const descriptor = opcode(code);
363
+ const descriptor = OPCODES.get(code);
365
364
  if (descriptor === undefined) {
366
365
  issue = `Unsupported CIL opcode 0x${code.toString(16)} at IL offset ${String(start)}`;
367
366
  break;
@@ -2,7 +2,8 @@ import { createHash } from "node:crypto";
2
2
  import { createReadStream } from "node:fs";
3
3
  import { readFile } from "node:fs/promises";
4
4
  import { createAnalysisExecution, } from "../application/AnalysisProvider.js";
5
- import { MANAGED_STATIC_PROVIDER_IDENTITY as IDENTITY, managedStaticCapabilities, } from "./ManagedStaticProviderMetadata.js";
5
+ import { managedStaticCapabilities } from "./ManagedStaticProviderMetadata.js";
6
+ import { MANAGED_STATIC_PROVIDER } from "../application/InvestigationProviders.js";
6
7
  import { MANAGED_TOOL_CONTRACTS, managedTargetInputSchema, } from "../contracts/managed/managedToolContracts.js";
7
8
  import { AnalysisCancelledError, AnalysisCapabilityUnavailableError, } from "../domain/analysisErrorCore.js";
8
9
  import { EvidenceIntegrityError } from "../domain/evidenceErrors.js";
@@ -15,7 +16,7 @@ import { inspectManagedNativeBoundariesBytes } from "./ManagedNativeBoundaryInsp
15
16
  export class ManagedStaticProvider {
16
17
  #capabilities = managedStaticCapabilities();
17
18
  identity() {
18
- return IDENTITY;
19
+ return MANAGED_STATIC_PROVIDER;
19
20
  }
20
21
  capabilities() {
21
22
  return this.#capabilities;
@@ -32,11 +33,11 @@ class ManagedStaticClient {
32
33
  }
33
34
  async execute(operation, parameters, options) {
34
35
  if (operation === "health")
35
- return ok(createAnalysisExecution(null, IDENTITY));
36
+ return ok(createAnalysisExecution(null, MANAGED_STATIC_PROVIDER));
36
37
  if (!isManagedOperation(operation))
37
- return err(new AnalysisCapabilityUnavailableError(IDENTITY.id, operation, "Operation is not implemented by the managed static provider."));
38
+ return err(new AnalysisCapabilityUnavailableError(MANAGED_STATIC_PROVIDER.id, operation, "Operation is not implemented by the managed static provider."));
38
39
  if (this.target.format !== "pe")
39
- return err(new AnalysisCapabilityUnavailableError(IDENTITY.id, operation, `Managed static triage requires a PE target; observed ${this.target.format}.`));
40
+ return err(new AnalysisCapabilityUnavailableError(MANAGED_STATIC_PROVIDER.id, operation, `Managed static triage requires a PE target; observed ${this.target.format}.`));
40
41
  try {
41
42
  if (options?.signal?.aborted === true)
42
43
  return err(new AnalysisCancelledError(operation));
@@ -51,7 +52,7 @@ class ManagedStaticClient {
51
52
  throw new TypeError("Managed snapshot bytes are unavailable");
52
53
  this.#snapshotBytes = bytes;
53
54
  const result = inspectManagedOperation(operation, parameters, bytes, this.target);
54
- return ok(createAnalysisExecution(result, IDENTITY, {
55
+ return ok(createAnalysisExecution(result, MANAGED_STATIC_PROVIDER, {
55
56
  rawResult: null,
56
57
  limitations: result.limitations,
57
58
  subject: this.target,
@@ -61,7 +62,9 @@ class ManagedStaticClient {
61
62
  catch (cause) {
62
63
  if (options?.signal?.aborted === true)
63
64
  return err(new AnalysisCancelledError(operation));
64
- return err(new ProviderAdapterError(IDENTITY.id, operation, { cause }));
65
+ return err(new ProviderAdapterError(MANAGED_STATIC_PROVIDER.id, operation, {
66
+ cause,
67
+ }));
65
68
  }
66
69
  }
67
70
  close() {
@@ -1,11 +1,8 @@
1
1
  import { MANAGED_STATIC_PROVIDER } from "../application/InvestigationProviders.js";
2
2
  import { MANAGED_TOOL_CONTRACTS, } from "../contracts/managed/managedToolContracts.js";
3
- /** Identity of execution-free managed metadata observations. */
4
- export const MANAGED_STATIC_PROVIDER_IDENTITY = Object.freeze(MANAGED_STATIC_PROVIDER);
5
- const IDENTITY = MANAGED_STATIC_PROVIDER_IDENTITY;
6
3
  /** Declare managed metadata coverage without opening or loading an assembly. */
7
4
  export const managedStaticCapabilities = () => Object.freeze(MANAGED_TOOL_CONTRACTS.map((contract) => Object.freeze({
8
- provider: IDENTITY,
5
+ provider: MANAGED_STATIC_PROVIDER,
9
6
  operation: contract.name,
10
7
  available: true,
11
8
  reason: null,
@@ -0,0 +1,41 @@
1
+ import { constants } from "node:fs";
2
+ import { open } from "node:fs/promises";
3
+ /** Identifies a selected path that cannot be read as one regular file. */
4
+ export class NonRegularFileReadError extends Error {
5
+ path;
6
+ code;
7
+ constructor(path, directory) {
8
+ super(`Selected input must be a regular file: ${path}`);
9
+ this.path = path;
10
+ this.name = "NonRegularFileReadError";
11
+ this.code = directory ? "EISDIR" : "ENOTFILE";
12
+ }
13
+ }
14
+ /** Open and verify the selected file descriptor without blocking on pipes. */
15
+ export const openRegularFile = async (path, options) => {
16
+ options.signal?.throwIfAborted();
17
+ const handle = await open(path, constants.O_RDONLY |
18
+ (constants.O_NONBLOCK ?? 0) |
19
+ (constants.O_NOCTTY ?? 0) |
20
+ (options.symlinks === "reject" ? (constants.O_NOFOLLOW ?? 0) : 0));
21
+ try {
22
+ options.signal?.throwIfAborted();
23
+ const metadata = await handle.stat();
24
+ options.signal?.throwIfAborted();
25
+ if (!metadata.isFile())
26
+ throw new NonRegularFileReadError(path, metadata.isDirectory());
27
+ return handle;
28
+ }
29
+ catch (cause) {
30
+ await handle.close().catch(() => undefined);
31
+ throw cause;
32
+ }
33
+ };
34
+ /** Compare one regular file's identity and mutable metadata across a read. */
35
+ export const sameRegularFileState = (expected, observed) => observed.isFile() &&
36
+ expected.dev === observed.dev &&
37
+ expected.ino === observed.ino &&
38
+ expected.size === observed.size &&
39
+ expected.mode === observed.mode &&
40
+ expected.mtimeMs === observed.mtimeMs &&
41
+ expected.ctimeMs === observed.ctimeMs;
@@ -1,7 +1,7 @@
1
1
  /** Generated from package.json and package-lock.json; do not edit. */
2
2
  export const PACKAGE_METADATA = {
3
3
  name: "rea-agents",
4
- version: "6.2.0", // x-release-please-version
4
+ version: "6.3.0", // x-release-please-version
5
5
  serverSdkVersion: "2.3.1",
6
6
  clientSdkVersion: "2.3.1",
7
7
  coreSdkVersion: "2.3.1",
@@ -10,6 +10,7 @@ import { ProviderRunLineage } from "../process/ProviderRunLineage.js";
10
10
  import { ProviderProcessSupervisor, } from "../process/ProviderProcess.js";
11
11
  import { DEFAULT_GHIDRA_STARTUP_TIMEOUT_MS } from "../config/ghidraStartupTimeout.js";
12
12
  import { createGhidraDiagnostics } from "./GhidraDiagnostics.js";
13
+ import { GhidraLaunchError } from "./GhidraLauncher.js";
13
14
  import { GhidraResponseBuffer } from "./GhidraResponseBuffer.js";
14
15
  import { GhidraResponseRouter } from "./GhidraResponseRouter.js";
15
16
  import { GhidraRequestQueue } from "./GhidraRequestQueue.js";
@@ -290,8 +291,17 @@ export class GhidraClient {
290
291
  }, { signal: deadline.signal })
291
292
  .catch((cause) => err(this.#failure("start", "Ghidra launcher failed", cause)));
292
293
  if (!launched.ok) {
294
+ const partialLaunch = launched.error instanceof GhidraLaunchError
295
+ ? launched.error.partialLaunch
296
+ : undefined;
297
+ if (partialLaunch !== undefined) {
298
+ this.#launch = partialLaunch;
299
+ this.#process = new ProviderProcessSupervisor(partialLaunch, {
300
+ onDiagnostic: (event) => this.#onProcessDiagnostic(event),
301
+ });
302
+ }
293
303
  const failure = deadline.signal.aborted
294
- ? this.#interruptionFailure(deadline)
304
+ ? this.#interruptionFailure(deadline, launched.error)
295
305
  : launched.error instanceof GhidraSessionError
296
306
  ? launched.error
297
307
  : this.#failure("start", launched.error.message, launched.error);
@@ -519,10 +529,10 @@ export class GhidraClient {
519
529
  const failure = this.#interruptionFailure(deadline);
520
530
  return this.#startupFailure(failure);
521
531
  }
522
- #interruptionFailure(deadline) {
532
+ #interruptionFailure(deadline, cause) {
523
533
  return deadline.interruption === "cancelled"
524
- ? this.#failure("cancelled", "Ghidra startup was cancelled")
525
- : this.#failure("timeout", "Ghidra startup deadline elapsed", undefined, {
534
+ ? this.#failure("cancelled", "Ghidra startup was cancelled", cause)
535
+ : this.#failure("timeout", "Ghidra startup deadline elapsed", cause, {
526
536
  timeoutMs: deadline.timeoutMs,
527
537
  });
528
538
  }
@@ -1,14 +1,84 @@
1
1
  import { snapshotEnvironment } from "../process/snapshotEnvironment.js";
2
2
  import { spawnSync } from "node:child_process";
3
3
  import { constants } from "node:fs";
4
- import { accessSync, readFileSync } from "node:fs";
4
+ import { accessSync, closeSync, fstatSync, openSync, readFileSync, readSync, statSync, } from "node:fs";
5
5
  import { posix, win32 } from "node:path";
6
+ import { parseExecutableHeader, } from "../domain/binaryTarget.js";
6
7
  import { ghidraApplicationProperties, ghidraJavaMajorAccepted, ghidraJavaRangeLabel, ghidraJavaRequirement, ghidraReleaseAccepted, ghidraReleaseCompatibility, ghidraReleaseLine, ghidraVersionDetail, SUPPORTED_GHIDRA_JAVA_MAJOR, SUPPORTED_GHIDRA_VERSION, } from "./GhidraInstallationPolicy.js";
7
8
  const NATIVE_PLATFORMS = {
8
9
  linux: { x64: "linux_x86_64", arm64: "linux_arm_64" },
9
10
  darwin: { x64: "mac_x86_64", arm64: "mac_arm_64" },
10
11
  win32: { x64: "win_x86_64" },
11
12
  };
13
+ /** Executable format each supported host runs its own Ghidra native tools as. */
14
+ const NATIVE_TOOL_FORMATS = {
15
+ linux: "elf",
16
+ darwin: "mach-o",
17
+ win32: "pe",
18
+ };
19
+ /** Node architecture identifiers as the executable-header parser reports them. */
20
+ const NATIVE_ARCHITECTURES = [
21
+ { node: "x64", binary: "x86_64" },
22
+ { node: "ia32", binary: "x86" },
23
+ { node: "arm", binary: "arm" },
24
+ { node: "arm64", binary: "arm64" },
25
+ ];
26
+ /** Bound installation discovery reads; larger headers remain unknown. */
27
+ const EXECUTABLE_HEADER_PROBE_BYTES = 4096;
28
+ /**
29
+ * Judge a native decompiler by its own executable header instead of the
30
+ * directory name that admits it.
31
+ *
32
+ * Only a header that positively contradicts the host is `incompatible`. An
33
+ * absent, unreadable, truncated, or unrecognized header stays `unknown`, so a
34
+ * file the other checks already accept is never refused on missing evidence.
35
+ */
36
+ const assessNativeDecompiler = (path, platform, architecture, probe) => {
37
+ const expectedFormat = NATIVE_TOOL_FORMATS[platform];
38
+ const expectedArchitecture = NATIVE_ARCHITECTURES.find(({ node }) => node === architecture)?.binary;
39
+ if (expectedFormat === undefined || expectedArchitecture === undefined)
40
+ return {
41
+ status: "unknown",
42
+ detail: `${path} is executable, but no native tool format is defined for ${platform}/${architecture}`,
43
+ };
44
+ let parsed = probe === undefined
45
+ ? undefined
46
+ : parseExecutableHeader(probe.bytes, architecture, probe.size);
47
+ if (probe !== undefined && parsed !== undefined && !parsed.ok) {
48
+ // FAT parsing selects a host slice. A validated foreign slice can still
49
+ // prove that its complete architecture table excludes this host. If the
50
+ // host is listed but its slice is malformed, preserve the unknown outcome.
51
+ for (const { node } of NATIVE_ARCHITECTURES) {
52
+ if (node === architecture)
53
+ continue;
54
+ const alternative = parseExecutableHeader(probe.bytes, node, probe.size);
55
+ if (alternative.ok &&
56
+ !alternative.value.availableArchitectures.includes(expectedArchitecture)) {
57
+ parsed = alternative;
58
+ break;
59
+ }
60
+ }
61
+ }
62
+ if (parsed === undefined || !parsed.ok)
63
+ return {
64
+ status: "unknown",
65
+ detail: `${path} is executable, but its executable format for ${platform}/${architecture} could not be established`,
66
+ };
67
+ const observed = `${parsed.value.format} ${parsed.value.architecture}`;
68
+ const expected = `${expectedFormat} ${expectedArchitecture}`;
69
+ if (observed === expected)
70
+ return {
71
+ status: "compatible",
72
+ detail: `${path} reports ${observed}, which matches ${platform}/${architecture}`,
73
+ };
74
+ const nativeDirectory = NATIVE_PLATFORMS[platform]?.[architecture];
75
+ const executable = platform === "win32" ? "decompile.exe" : "decompile";
76
+ return {
77
+ status: "incompatible",
78
+ detail: `${path} reports ${observed}, but ${platform}/${architecture} requires ${expected}`,
79
+ remediation: `Provide an executable Ghidra/Features/Decompiler/os/${nativeDirectory ?? "<platform>"}/${executable} built for ${expected}, or its build/os equivalent. REA does not build or install native tools.`,
80
+ };
81
+ };
12
82
  /** Inspect an installation without importing a target or modifying Ghidra. */
13
83
  export const inspectGhidraInstallation = (options, host = systemGhidraInstallationHost()) => {
14
84
  const platform = options.platform ?? process.platform;
@@ -137,14 +207,7 @@ const installationChecks = ({ coordinates, platform, architecture, javaHome, hos
137
207
  detail: coordinates.analyzeHeadlessPath ?? "analyzeHeadless unavailable",
138
208
  remediation: "Restore support/analyzeHeadless or support/analyzeHeadless.bat from the official Ghidra release.",
139
209
  }),
140
- installationCheck({
141
- name: "native_decompiler",
142
- passed: coordinates.nativeDecompilerPath !== null,
143
- code: "executable_missing",
144
- detail: coordinates.nativeDecompilerPath ??
145
- `Matching native decompiler for ${platform}/${architecture} was not found`,
146
- remediation: `Provide an executable Ghidra/Features/Decompiler/os/${NATIVE_PLATFORMS[platform]?.[architecture] ?? "<platform>"}/${platform === "win32" ? "decompile.exe" : "decompile"}, or its build/os equivalent. REA does not build or install native tools.`,
147
- }),
210
+ nativeDecompilerCheck({ coordinates, platform, architecture, host }),
148
211
  javaCheck(coordinates.java, coordinates.javaCommand, javaHome, coordinates.properties),
149
212
  ];
150
213
  /**
@@ -226,6 +289,23 @@ const installationCheck = (options) => options.passed
226
289
  detail: options.detail,
227
290
  remediation: options.remediation,
228
291
  };
292
+ /** Require a present native decompiler without positively incompatible bytes. */
293
+ const nativeDecompilerCheck = ({ coordinates, platform, architecture, host, }) => {
294
+ const path = coordinates.nativeDecompilerPath;
295
+ const compatibility = path === null
296
+ ? undefined
297
+ : assessNativeDecompiler(path, platform, architecture, host.executableHeader?.(path));
298
+ return installationCheck({
299
+ name: "native_decompiler",
300
+ passed: path !== null && compatibility?.status !== "incompatible",
301
+ code: "executable_missing",
302
+ detail: compatibility?.detail ??
303
+ `Matching native decompiler for ${platform}/${architecture} was not found`,
304
+ remediation: compatibility?.status === "incompatible"
305
+ ? compatibility.remediation
306
+ : `Provide an executable Ghidra/Features/Decompiler/os/${NATIVE_PLATFORMS[platform]?.[architecture] ?? "<platform>"}/${platform === "win32" ? "decompile.exe" : "decompile"}, or its build/os equivalent. REA does not build or install native tools.`,
307
+ });
308
+ };
229
309
  const versionCheck = (properties) => {
230
310
  const raw = properties?.providerVersion;
231
311
  return installationCheck({
@@ -280,10 +360,31 @@ const systemGhidraInstallationHost = () => ({
280
360
  return undefined;
281
361
  }
282
362
  },
363
+ executableHeader(path) {
364
+ let handle;
365
+ try {
366
+ handle = openSync(path, constants.O_RDONLY | constants.O_NONBLOCK | constants.O_NOCTTY);
367
+ const metadata = fstatSync(handle);
368
+ if (!metadata.isFile())
369
+ return undefined;
370
+ const bytes = Buffer.alloc(EXECUTABLE_HEADER_PROBE_BYTES);
371
+ const read = readSync(handle, bytes, 0, bytes.length, 0);
372
+ return { bytes: bytes.subarray(0, read), size: metadata.size };
373
+ }
374
+ catch (cause) {
375
+ // best-effort cleanup: a header that cannot be read stays unknown.
376
+ void cause;
377
+ return undefined;
378
+ }
379
+ finally {
380
+ if (handle !== undefined)
381
+ closeSync(handle);
382
+ }
383
+ },
283
384
  executable(path) {
284
385
  try {
285
386
  accessSync(path, constants.X_OK);
286
- return true;
387
+ return statSync(path).isFile();
287
388
  }
288
389
  catch (cause) {
289
390
  // best-effort cleanup: optional executable probing; failure means missing.