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
@@ -1,4 +1,5 @@
1
1
  import { access, copyFile, mkdir, readFile, rm, writeFile, } from "node:fs/promises";
2
+ import { constants as fsConstants } from "node:fs";
2
3
  import { homedir } from "node:os";
3
4
  import { dirname, resolve } from "node:path";
4
5
  import { z } from "zod";
@@ -7,7 +8,7 @@ import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
7
8
  import { PRODUCT_IDENTITY } from "../identity.js";
8
9
  import { supportsNodeVersion } from "../domain/runtimeVersion.js";
9
10
  import { resolveClientConfigTransactionPath } from "./ClientConfigPath.js";
10
- import { runDoctor, systemDoctorHost } from "./Doctor.js";
11
+ import { runDoctor, systemDoctorHost, } from "./Doctor.js";
11
12
  import { installLinuxHopper, readLinuxDistribution, } from "./LinuxHopper.js";
12
13
  import { installMacHopper } from "./MacHopper.js";
13
14
  import { configureDetectedClients } from "./SetupClients.js";
@@ -36,12 +37,14 @@ export const runSetup = async (options, host = systemSetupHost(), confirm) => {
36
37
  doctor: await host.doctor(),
37
38
  remediation,
38
39
  });
39
- const unsupported = await hostRemediation(host);
40
+ const unsupported = await hostRemediation(host, options.installHopper);
40
41
  if (unsupported !== undefined)
41
42
  return fail(unsupported);
42
43
  let hopperPath = await host.hopperPath();
43
- const discovery = await discoverSetupActions(host, hopperPath);
44
- const { detectedClients, installHopper, installSkill } = discovery;
44
+ let providerEnvironment = await initialProviderEnvironment(host, hopperPath);
45
+ const discovery = await discoverSetupActions(host, providerEnvironment, options.installHopper);
46
+ const { installHopper, installSkill } = discovery;
47
+ let { detectedClients } = discovery;
45
48
  plannedActions = discovery.plannedActions;
46
49
  let approved = options.approved || plannedActions.length === 0;
47
50
  let interactiveApproval = false;
@@ -59,7 +62,7 @@ export const runSetup = async (options, host = systemSetupHost(), confirm) => {
59
62
  remediation: "Review the setup plan, then rerun interactively or with --yes.",
60
63
  };
61
64
  if (installHopper && (interactiveApproval || options.installHopper)) {
62
- const installed = await host.installHopper();
65
+ const installed = await host.installHopper(options.installHopper);
63
66
  if (installed.status === "failed")
64
67
  return {
65
68
  status: "needs_human",
@@ -71,12 +74,17 @@ export const runSetup = async (options, host = systemSetupHost(), confirm) => {
71
74
  remediation: installed.remediation,
72
75
  };
73
76
  hopperPath = installed.launcherPath;
77
+ providerEnvironment = {
78
+ ...providerEnvironment,
79
+ HOPPER_LAUNCHER_PATH: installed.launcherPath,
80
+ };
74
81
  appliedActions.push("installed_hopper");
82
+ detectedClients = await filterClientsNeedingConfigure(host, detectedClients, providerEnvironment, registrationCommand());
75
83
  }
76
84
  const clientFailure = await configureDetectedClients({
77
85
  host,
78
86
  detectedClients,
79
- hopperPath,
87
+ providerEnvironment,
80
88
  command: registrationCommand(),
81
89
  clients,
82
90
  appliedActions,
@@ -101,12 +109,14 @@ export const runSetup = async (options, host = systemSetupHost(), confirm) => {
101
109
  ...(remediation === undefined ? {} : { remediation }),
102
110
  };
103
111
  };
104
- const discoverSetupActions = async (host, hopperPath) => {
112
+ const discoverSetupActions = async (host, providerEnvironment, forceHopperInstall) => {
105
113
  const initialDoctor = await host.doctor();
106
114
  const linuxHopperRepairNeeded = initialDoctor.checks.some(({ name, ok, detail }) => !ok &&
107
115
  (name === "hopper-demo-runtime" ||
108
116
  (name === "hopper-version" && detail === "/opt/hopper/bin/Hopper")));
109
- const installHopper = hopperPath === undefined || linuxHopperRepairNeeded;
117
+ const installHopper = forceHopperInstall ||
118
+ Object.keys(providerEnvironment).length === 0 ||
119
+ linuxHopperRepairNeeded;
110
120
  const [detectedClients, installSkill] = await Promise.all([
111
121
  host.detectedClients(),
112
122
  host.skillNeedsInstall(),
@@ -114,8 +124,8 @@ const discoverSetupActions = async (host, hopperPath) => {
114
124
  const command = registrationCommand();
115
125
  const clients = installHopper
116
126
  ? detectedClients
117
- : await filterClientsNeedingConfigure(host, detectedClients, hopperPath, command);
118
- const plannedActions = setupPlan(host.platform, installHopper, installSkill, clients);
127
+ : await filterClientsNeedingConfigure(host, detectedClients, providerEnvironment, command);
128
+ const plannedActions = setupPlan(host.platform, installHopper, installSkill, clients, providerEnvironment);
119
129
  return {
120
130
  installHopper,
121
131
  installSkill,
@@ -123,10 +133,14 @@ const discoverSetupActions = async (host, hopperPath) => {
123
133
  plannedActions,
124
134
  };
125
135
  };
126
- const filterClientsNeedingConfigure = async (host, detectedClients, hopperPath, command) => {
127
- const needs = await Promise.all(detectedClients.map((client) => host.clientNeedsConfigure(client, hopperPath, command)));
136
+ const filterClientsNeedingConfigure = async (host, detectedClients, providerEnvironment, command) => {
137
+ const needs = await Promise.all(detectedClients.map((client) => host.clientNeedsConfigure(client, providerEnvironment, command)));
128
138
  return detectedClients.filter((_, index) => needs[index]);
129
139
  };
140
+ const initialProviderEnvironment = async (host, hopperPath) => ({
141
+ ...(await host.providerEnvironment?.()),
142
+ ...(hopperPath === undefined ? {} : { HOPPER_LAUNCHER_PATH: hopperPath }),
143
+ });
130
144
  const finalSetupRemediation = (platform, installedHopper, healthy, hopperPath) => {
131
145
  if (platform === "darwin" && installedHopper)
132
146
  return "Open Hopper, choose its demo mode or activate a license, then rerun rea doctor --json.";
@@ -136,7 +150,7 @@ const finalSetupRemediation = (platform, installedHopper, healthy, hopperPath) =
136
150
  ? "Hopper is optional for non-Hopper providers. Rerun with --yes --install-hopper for deep native analysis."
137
151
  : "Run rea doctor and apply each reported remediation.";
138
152
  };
139
- const hostRemediation = async (host) => {
153
+ const hostRemediation = async (host, forceHopperInstall) => {
140
154
  if (host.platform !== "darwin" && host.platform !== "linux")
141
155
  return "REA supports Hopper on macOS and selected 64-bit Linux distributions.";
142
156
  if (!supportsNodeVersion(host.nodeVersion))
@@ -147,34 +161,47 @@ const hostRemediation = async (host) => {
147
161
  ? "Upgrade to macOS 12 or newer."
148
162
  : undefined;
149
163
  }
150
- return (await host.linuxDistribution())?.supported === true
151
- ? undefined
152
- : "REA supports Hopper on Ubuntu 24.04+, Fedora 41+, and 64-bit Arch Linux.";
164
+ if ((await host.linuxDistribution())?.supported === true)
165
+ return undefined;
166
+ const providerEnvironment = await host.providerEnvironment?.();
167
+ if (!forceHopperInstall &&
168
+ providerEnvironment !== undefined &&
169
+ Object.keys(providerEnvironment).length > 0)
170
+ return undefined;
171
+ return "Automated Hopper setup supports Ubuntu 24.04+, Fedora 41+, and 64-bit Arch Linux; configure an existing supported provider instead.";
153
172
  };
154
173
  /** Production setup effects for Hopper, agent configuration, and the canonical skill directory. */
155
- const systemSetupHost = () => {
156
- const doctorHost = systemDoctorHost();
174
+ export const systemSetupHost = (doctorHost = systemDoctorHost()) => {
157
175
  return {
158
176
  platform: process.platform,
159
177
  nodeVersion: process.versions.node,
160
178
  macosVersion: () => doctorHost.macosVersion(),
161
179
  linuxDistribution: readLinuxDistribution,
162
180
  hopperPath: async () => (await runDoctor(undefined, doctorHost)).hopperPath,
163
- installHopper: async () => {
181
+ providerEnvironment: async () => {
182
+ const diagnosis = await runDoctor(undefined, doctorHost);
183
+ return {
184
+ ...providerRegistrationEnvironment(diagnosis.providerInspections ?? []),
185
+ ...(diagnosis.hopperPath === undefined
186
+ ? {}
187
+ : { HOPPER_LAUNCHER_PATH: diagnosis.hopperPath }),
188
+ };
189
+ },
190
+ installHopper: async (replaceExisting) => {
164
191
  const result = process.platform === "linux"
165
192
  ? await installLinuxHopper()
166
- : await installMacHopper();
193
+ : await installMacHopper({ replaceExisting });
167
194
  if (result.status === "installed")
168
195
  return result;
169
196
  return setupInstallFailure(result.reason);
170
197
  },
171
198
  detectedClients: () => detectClients(homedir()),
172
- configureClient: (client, hopperPath, command) => client.format === "unsupported"
199
+ configureClient: (client, providerEnvironment, command) => client.format === "unsupported"
173
200
  ? Promise.resolve({ status: "skipped" })
174
201
  : client.format === "toml"
175
- ? configureTomlClient(client, hopperPath, command)
176
- : configureJsonClient(client, hopperPath, command),
177
- clientNeedsConfigure: (client, hopperPath, command) => clientConfigurationAligned(client, hopperPath, command).then((aligned) => !aligned),
202
+ ? configureTomlClient(client, providerEnvironment, command)
203
+ : configureJsonClient(client, providerEnvironment, command),
204
+ clientNeedsConfigure: (client, providerEnvironment, command) => clientConfigurationAligned(client, providerEnvironment, command).then((aligned) => !aligned),
178
205
  skillNeedsInstall: () => canonicalSkillNeedsInstall(homedir()),
179
206
  installSkill: () => installCanonicalSkill(homedir()),
180
207
  doctor: () => runDoctor(undefined, doctorHost),
@@ -189,7 +216,7 @@ export const detectClients = async (home) => {
189
216
  return detected;
190
217
  };
191
218
  /** Back up, atomically update, and semantically read back one JSON MCP configuration. */
192
- export const configureJsonClient = async (client, hopperPath, command = [
219
+ export const configureJsonClient = async (client, environment = {}, command = [
193
220
  "npx",
194
221
  "-y",
195
222
  PRODUCT_IDENTITY.packageName,
@@ -215,25 +242,15 @@ export const configureJsonClient = async (client, hopperPath, command = [
215
242
  catch {
216
243
  return { status: "failed", reason: "readback" };
217
244
  }
218
- const desired = {
219
- command: command[0] ?? PRODUCT_IDENTITY.cliBinary,
220
- args: command.slice(1),
221
- ...(hopperPath === undefined
222
- ? {}
223
- : { env: { HOPPER_LAUNCHER_PATH: hopperPath } }),
224
- };
245
+ const desired = clientConfigurationDesired(normalizeProviderEnvironment(environment), command);
225
246
  if (JSON.stringify(servers[PRODUCT_IDENTITY.mcpServerKey]) ===
226
247
  JSON.stringify(desired))
227
248
  return { status: "unchanged" };
228
249
  let backupPath;
229
250
  if (original !== undefined) {
230
251
  backupPath = `${client.configPath}.rea.backup`;
231
- try {
232
- await copyFile(transactionPath, backupPath);
233
- }
234
- catch {
252
+ if (!(await preserveConfigBackup(transactionPath, backupPath)))
235
253
  return { status: "failed", reason: "backup" };
236
- }
237
254
  }
238
255
  document.mcpServers = {
239
256
  ...servers,
@@ -268,7 +285,7 @@ export const configureJsonClient = async (client, hopperPath, command = [
268
285
  };
269
286
  };
270
287
  /** Back up, atomically update, and semantically read back Codex TOML configuration. */
271
- export const configureTomlClient = async (client, hopperPath, command = [
288
+ export const configureTomlClient = async (client, environment = {}, command = [
272
289
  "npx",
273
290
  "-y",
274
291
  PRODUCT_IDENTITY.packageName,
@@ -294,24 +311,14 @@ export const configureTomlClient = async (client, hopperPath, command = [
294
311
  catch {
295
312
  return { status: "failed", reason: "readback" };
296
313
  }
297
- const desired = {
298
- command: command[0] ?? PRODUCT_IDENTITY.cliBinary,
299
- args: command.slice(1),
300
- ...(hopperPath === undefined
301
- ? {}
302
- : { env: { HOPPER_LAUNCHER_PATH: hopperPath } }),
303
- };
314
+ const desired = clientConfigurationDesired(normalizeProviderEnvironment(environment), command);
304
315
  if (JSON.stringify(servers[PRODUCT_IDENTITY.mcpServerKey]) ===
305
316
  JSON.stringify(desired))
306
317
  return { status: "unchanged" };
307
318
  const backupPath = original === undefined ? undefined : `${client.configPath}.rea.backup`;
308
- try {
309
- if (backupPath !== undefined)
310
- await copyFile(transactionPath, backupPath);
311
- }
312
- catch {
319
+ if (backupPath !== undefined &&
320
+ !(await preserveConfigBackup(transactionPath, backupPath)))
313
321
  return { status: "failed", reason: "backup" };
314
- }
315
322
  document.mcp_servers = {
316
323
  ...servers,
317
324
  [PRODUCT_IDENTITY.mcpServerKey]: desired,
@@ -345,6 +352,15 @@ const exists = async (path) => {
345
352
  return false;
346
353
  }
347
354
  };
355
+ const preserveConfigBackup = async (source, destination) => {
356
+ try {
357
+ await copyFile(source, destination, fsConstants.COPYFILE_EXCL);
358
+ return true;
359
+ }
360
+ catch (cause) {
361
+ return cause instanceof Error && "code" in cause && cause.code === "EEXIST";
362
+ }
363
+ };
348
364
  const objectSchema = z.record(z.string(), z.unknown());
349
365
  const parseOptionalObject = (value) => value === undefined ? {} : objectSchema.parse(value);
350
366
  const parseObject = (text) => objectSchema.parse(JSON.parse(text));
@@ -360,15 +376,16 @@ const restoreConfig = async (path, original) => {
360
376
  // The backup remains available for the remediation reported by setup.
361
377
  }
362
378
  };
363
- const clientConfigurationDesired = (hopperPath, command) => ({
364
- command: command[0] ?? PRODUCT_IDENTITY.cliBinary,
365
- args: command.slice(1),
366
- ...(hopperPath === undefined
367
- ? {}
368
- : { env: { HOPPER_LAUNCHER_PATH: hopperPath } }),
369
- });
370
- const clientConfigurationAligned = async (client, hopperPath, command) => {
371
- const desired = clientConfigurationDesired(hopperPath, command);
379
+ const clientConfigurationDesired = (providerEnvironment, command) => {
380
+ const environment = Object.fromEntries(Object.entries(providerEnvironment).sort(([left], [right]) => left.localeCompare(right)));
381
+ return {
382
+ command: command[0] ?? PRODUCT_IDENTITY.cliBinary,
383
+ args: command.slice(1),
384
+ ...(Object.keys(environment).length === 0 ? {} : { env: environment }),
385
+ };
386
+ };
387
+ const clientConfigurationAligned = async (client, providerEnvironment, command) => {
388
+ const desired = clientConfigurationDesired(providerEnvironment, command);
372
389
  try {
373
390
  const original = await readFile(client.configPath, "utf8");
374
391
  if (client.format === "toml") {
@@ -386,3 +403,10 @@ const clientConfigurationAligned = async (client, hopperPath, command) => {
386
403
  return false;
387
404
  }
388
405
  };
406
+ const normalizeProviderEnvironment = (environment) => typeof environment === "string"
407
+ ? { HOPPER_LAUNCHER_PATH: environment }
408
+ : environment;
409
+ const providerRegistrationEnvironment = (inspections) => Object.fromEntries(inspections
410
+ .filter(({ available }) => available)
411
+ .flatMap(({ registrationEnvironment }) => Object.entries(registrationEnvironment))
412
+ .sort(([left], [right]) => left.localeCompare(right)));
@@ -11,7 +11,7 @@ const failedConfigurationMessage = (reason) => {
11
11
  export const configureDetectedClients = async (options) => {
12
12
  let failure;
13
13
  for (const client of options.detectedClients) {
14
- const result = await options.host.configureClient(client, options.hopperPath, options.command);
14
+ const result = await options.host.configureClient(client, options.providerEnvironment, options.command);
15
15
  options.clients[client.name] = result;
16
16
  if (result.status === "failed")
17
17
  failure ??= failedConfigurationMessage(result.reason);
@@ -1,5 +1,5 @@
1
1
  /** Build the complete setup mutation plan before approval. */
2
- export const setupPlan = (platform, installHopper, installSkill, clients) => [
2
+ export const setupPlan = (platform, installHopper, installSkill, clients, providerEnvironment) => [
3
3
  ...(installHopper
4
4
  ? [
5
5
  {
@@ -19,7 +19,7 @@ export const setupPlan = (platform, installHopper, installSkill, clients) => [
19
19
  .map((client) => ({
20
20
  kind: "configure_client",
21
21
  target: client.configPath,
22
- detail: `Add the REA MCP registration for ${client.name}; preserve unrelated configuration.`,
22
+ detail: clientConfigurationDetail(client.name, providerEnvironment),
23
23
  external: false,
24
24
  })),
25
25
  ...(installSkill
@@ -33,3 +33,10 @@ export const setupPlan = (platform, installHopper, installSkill, clients) => [
33
33
  ]
34
34
  : []),
35
35
  ];
36
+ const clientConfigurationDetail = (client, environment) => {
37
+ const entries = Object.entries(environment)
38
+ .sort(([left], [right]) => left.localeCompare(right))
39
+ .map(([name, value]) => `${name}=${value}`);
40
+ const suffix = entries.length === 0 ? "" : ` Environment: ${entries.join(", ")}.`;
41
+ return `Add the REA MCP registration for ${client}; preserve unrelated configuration.${suffix}`;
42
+ };
@@ -1,41 +1,65 @@
1
1
  import { join } from "node:path";
2
- /** Describe every client location that setup, doctor, or uninstall may inspect. */
3
- export const supportedClients = (home) => [
2
+ /** Stable product metadata used to derive setup discovery and documentation. */
3
+ export const SUPPORTED_CLIENT_DEFINITIONS = [
4
4
  {
5
5
  name: "claude_code",
6
- configPath: join(home, ".claude.json"),
7
- markerPath: join(home, ".claude"),
6
+ displayName: "Claude Code",
7
+ configPath: [".claude.json"],
8
+ markerPath: [".claude"],
9
+ format: "json",
8
10
  },
9
11
  {
10
12
  name: "claude_desktop",
11
- configPath: join(home, "Library/Application Support/Claude/claude_desktop_config.json"),
12
- markerPath: join(home, "Library/Application Support/Claude"),
13
+ displayName: "Claude Desktop",
14
+ configPath: [
15
+ "Library",
16
+ "Application Support",
17
+ "Claude",
18
+ "claude_desktop_config.json",
19
+ ],
20
+ markerPath: ["Library", "Application Support", "Claude"],
21
+ format: "json",
13
22
  },
14
23
  {
15
24
  name: "codex",
16
- configPath: join(home, ".codex/config.toml"),
17
- markerPath: join(home, ".codex"),
25
+ displayName: "Codex",
26
+ configPath: [".codex", "config.toml"],
27
+ markerPath: [".codex"],
18
28
  format: "toml",
19
29
  },
20
30
  {
21
31
  name: "cursor",
22
- configPath: join(home, ".cursor/mcp.json"),
23
- markerPath: join(home, ".cursor"),
32
+ displayName: "Cursor",
33
+ configPath: [".cursor", "mcp.json"],
34
+ markerPath: [".cursor"],
35
+ format: "json",
24
36
  },
25
37
  {
26
38
  name: "gemini_cli",
27
- configPath: join(home, ".gemini/settings.json"),
28
- markerPath: join(home, ".gemini"),
39
+ displayName: "Gemini CLI",
40
+ configPath: [".gemini", "settings.json"],
41
+ markerPath: [".gemini"],
42
+ format: "json",
29
43
  },
30
44
  {
31
45
  name: "windsurf",
32
- configPath: join(home, ".codeium/windsurf/mcp_config.json"),
33
- markerPath: join(home, ".codeium/windsurf"),
46
+ displayName: "Windsurf",
47
+ configPath: [".codeium", "windsurf", "mcp_config.json"],
48
+ markerPath: [".codeium", "windsurf"],
49
+ format: "json",
34
50
  },
35
51
  {
36
52
  name: "devin",
37
- configPath: join(home, ".devin"),
38
- markerPath: join(home, ".devin"),
53
+ displayName: "Devin",
54
+ configPath: [".devin"],
55
+ markerPath: [".devin"],
39
56
  format: "unsupported",
40
57
  },
41
58
  ];
59
+ /** Describe every client location that setup, doctor, or uninstall may inspect. */
60
+ export const supportedClients = (home) => SUPPORTED_CLIENT_DEFINITIONS.map((definition) => ({
61
+ name: definition.name,
62
+ configPath: join(home, ...definition.configPath),
63
+ markerPath: join(home, ...definition.markerPath),
64
+ format: definition.format,
65
+ }));
@@ -112,7 +112,7 @@ const inferUnixGlobalPrefix = (packageRoot) => {
112
112
  };
113
113
  const runCommand = (command, arguments_, output) => new Promise((resolveResult) => {
114
114
  const child = spawn(command, [...arguments_], {
115
- stdio: output === "human" ? "inherit" : ["inherit", process.stderr, "inherit"],
115
+ stdio: output === "human" ? "inherit" : ["inherit", "ignore", "inherit"],
116
116
  });
117
117
  child.once("error", () => resolveResult(false));
118
118
  child.once("exit", (code) => resolveResult(code === 0));
@@ -1,18 +1,23 @@
1
1
  import { BinarySession } from "./BinarySession.js";
2
2
  import { HopperProvider } from "../hopper/HopperProvider.js";
3
+ import { GhidraProvider } from "../ghidra/GhidraProvider.js";
3
4
  import { NativeMacOSProvider } from "../native/NativeMacOSProvider.js";
4
5
  import { ArtifactProvider } from "../artifacts/ArtifactProvider.js";
5
- import { CompositeProvider } from "./CompositeProvider.js";
6
+ import { ManagedStaticProvider } from "../dotnet/ManagedStaticProvider.js";
6
7
  import { silentLogger } from "../logger.js";
8
+ import { AnalysisProviderRegistry } from "./AnalysisProviderRegistry.js";
9
+ import { SessionProviderRouter } from "./SessionProviderRouter.js";
7
10
  /**
8
11
  * Compose the target-switching runtime shared directly by CLI and MCP adapters.
9
- * This is the sole production wiring point for Hopper so neither adapter shells
10
- * out to the other and both retain identical loader and bridge semantics.
12
+ * This is the sole production wiring point, so both adapters share identical
13
+ * provider selection, profile, lifecycle, and Evidence semantics.
11
14
  */
12
15
  export const createBinarySession = (config, logger = silentLogger) => {
13
- return new BinarySession(new CompositeProvider([
16
+ const hopper = new HopperProvider(config, logger);
17
+ const ghidra = new GhidraProvider(config, logger);
18
+ return new BinarySession(SessionProviderRouter.selectable(new AnalysisProviderRegistry([hopper, ghidra], config.analysisProvider), [
14
19
  new ArtifactProvider(config.artifactNativeMountEnabled, config.artifactIntegrityContinueEnabled),
15
20
  new NativeMacOSProvider(),
16
- new HopperProvider(config, logger),
17
- ]), { snapshotsEnabled: config.hopperLoaderArgs.length === 0 });
21
+ new ManagedStaticProvider(),
22
+ ]));
18
23
  };
@@ -15,13 +15,25 @@ export class AsarArtifactReader {
15
15
  this.path = path;
16
16
  }
17
17
  async *entries(signal) {
18
- const paths = listPackage(this.path, { isPack: false }).sort((left, right) => left.localeCompare(right, "en"));
18
+ let paths;
19
+ try {
20
+ paths = listPackage(this.path, { isPack: false }).sort((left, right) => left.localeCompare(right, "en"));
21
+ }
22
+ catch (cause) {
23
+ throw asarFailure(this.path, "inventory", cause);
24
+ }
19
25
  for (const listed of paths) {
20
26
  abortIfNeeded(signal);
21
27
  const path = listed.replace(/^\/+|\/+$/gu, "");
22
28
  if (path.length === 0)
23
29
  continue;
24
- const metadata = statFile(this.path, path, false);
30
+ let metadata;
31
+ try {
32
+ metadata = statFile(this.path, path, false);
33
+ }
34
+ catch (cause) {
35
+ throw asarFailure(this.path, `stat ${path}`, cause);
36
+ }
25
37
  const kind = "files" in metadata
26
38
  ? "directory"
27
39
  : "link" in metadata
@@ -54,7 +66,12 @@ export class AsarArtifactReader {
54
66
  abortIfNeeded(signal);
55
67
  if (entry.kind !== "file")
56
68
  return Promise.reject(new ArtifactReaderFailure("format", "ASAR entry is not a regular file"));
57
- return Promise.resolve(Readable.from(extractFile(this.path, entry.adapterKey, false)));
69
+ try {
70
+ return Promise.resolve(Readable.from(extractFile(this.path, entry.adapterKey, false)));
71
+ }
72
+ catch (cause) {
73
+ return Promise.reject(asarFailure(this.path, `read ${entry.path}`, cause));
74
+ }
58
75
  }
59
76
  close() {
60
77
  return Promise.resolve();
@@ -67,3 +84,6 @@ const abortIfNeeded = (signal) => {
67
84
  if (signal?.aborted === true)
68
85
  throw new ArtifactReaderFailure("cancelled", "ASAR operation cancelled");
69
86
  };
87
+ const asarFailure = (path, operation, cause) => cause instanceof ArtifactReaderFailure
88
+ ? cause
89
+ : new ArtifactReaderFailure("format", `Malformed or unreadable ASAR during ${operation}: ${path}`, { cause });
@@ -15,11 +15,13 @@ import { observeCdpSession } from "./CdpObservationSession.js";
15
15
  import { discoverWebMcp } from "./CdpWebMcpDiscovery.js";
16
16
  import { captureCdpScreenshot } from "./CdpScreenshot.js";
17
17
  import { comparePngScreenshots } from "./PngVisualDiff.js";
18
- const IDENTITY = {
18
+ /** Public identity committed by passive browser observations. */
19
+ export const CDP_BROWSER_PROVIDER_IDENTITY = Object.freeze({
19
20
  id: "rea-cdp-browser",
20
21
  name: "REA Chrome DevTools Protocol observation provider",
21
22
  version: "2",
22
- };
23
+ });
24
+ const IDENTITY = CDP_BROWSER_PROVIDER_IDENTITY;
23
25
  const CLEANUP_DOMAINS = ["Network", "Debugger", "Runtime", "Page"];
24
26
  /** Passive, origin-scoped browser observation through a user-owned CDP endpoint. */
25
27
  export class CdpBrowserProvider {
@@ -8,6 +8,7 @@ export class CdpCaptureEvents {
8
8
  input;
9
9
  allowedOrigins;
10
10
  scripts = new Map();
11
+ executionContextFrames = new Map();
11
12
  network = new Map();
12
13
  allowedWebSockets = new Set();
13
14
  console = [];
@@ -36,6 +37,7 @@ export class CdpCaptureEvents {
36
37
  this.originViolation = false;
37
38
  this.navigationDuringCapture = false;
38
39
  this.scripts.clear();
40
+ this.executionContextFrames.clear();
39
41
  this.network.clear();
40
42
  this.allowedWebSockets.clear();
41
43
  this.console.length = 0;
@@ -62,6 +64,15 @@ export class CdpCaptureEvents {
62
64
  if (params === undefined)
63
65
  return;
64
66
  switch (event.method) {
67
+ case "Runtime.executionContextCreated":
68
+ this.#executionContext(params);
69
+ break;
70
+ case "Runtime.executionContextDestroyed":
71
+ this.#destroyExecutionContext(params);
72
+ break;
73
+ case "Runtime.executionContextsCleared":
74
+ this.executionContextFrames.clear();
75
+ break;
65
76
  case "Debugger.scriptParsed":
66
77
  this.#script(params);
67
78
  break;
@@ -91,6 +102,37 @@ export class CdpCaptureEvents {
91
102
  break;
92
103
  }
93
104
  }
105
+ /** Resolve a transient execution context to one already authorized frame. */
106
+ frameForScript(script, allowedFrameIds) {
107
+ if (script.executionContextKey === null)
108
+ return null;
109
+ const frameId = this.executionContextFrames.get(script.executionContextKey);
110
+ return frameId !== undefined && allowedFrameIds.has(frameId)
111
+ ? frameId
112
+ : null;
113
+ }
114
+ #executionContext(params) {
115
+ const context = recordValue(params.context);
116
+ const identifier = numberValue(context?.id);
117
+ const frameId = stringValue(recordValue(context?.auxData)?.frameId);
118
+ if (identifier === undefined ||
119
+ !Number.isSafeInteger(identifier) ||
120
+ frameId === undefined ||
121
+ frameId.length > 256)
122
+ return;
123
+ const key = String(identifier);
124
+ if (this.executionContextFrames.size >= this.input.limits.max_scripts &&
125
+ !this.executionContextFrames.has(key)) {
126
+ this.completeness.truncate("scripts");
127
+ return;
128
+ }
129
+ this.executionContextFrames.set(key, frameId);
130
+ }
131
+ #destroyExecutionContext(params) {
132
+ const key = executionContextKey(params.executionContextId);
133
+ if (key !== null)
134
+ this.executionContextFrames.delete(key);
135
+ }
94
136
  #script(params) {
95
137
  const scriptId = stringValue(params.scriptId);
96
138
  const rawUrl = stringValue(params.url) ?? "";
@@ -120,6 +162,7 @@ export class CdpCaptureEvents {
120
162
  language: boundedText(params.scriptLanguage, 100),
121
163
  sourceMapUrl: sourceMap.sanitized,
122
164
  sourceMapRawUrl: sourceMap.raw,
165
+ executionContextKey: executionContextKey(params.executionContextId),
123
166
  });
124
167
  }
125
168
  #sourceMap(value, scriptUrl) {
@@ -501,6 +544,12 @@ const integerOrNull = (value) => {
501
544
  const number = numberValue(value);
502
545
  return number === undefined ? null : Math.max(0, Math.trunc(number));
503
546
  };
547
+ const executionContextKey = (value) => {
548
+ const identifier = numberValue(value);
549
+ return identifier !== undefined && Number.isSafeInteger(identifier)
550
+ ? String(identifier)
551
+ : null;
552
+ };
504
553
  const isJsonContentType = (headers) => {
505
554
  if (headers === undefined)
506
555
  return false;