rea-agents 2.0.0 → 2.1.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 (246) hide show
  1. package/README.md +78 -32
  2. package/bridge/ghidra/ReaGhidraBridge.java +96 -11
  3. package/dist/application/ArtifactExtraction.js +1 -1
  4. package/dist/application/ArtifactInventory/classify.js +42 -0
  5. package/dist/application/ArtifactInventory/hash.js +43 -0
  6. package/dist/application/ArtifactInventory/reader.js +41 -0
  7. package/dist/application/ArtifactInventory/scanCanonical.js +155 -0
  8. package/dist/application/ArtifactInventory/scanReader.js +124 -0
  9. package/dist/application/ArtifactInventory/types.js +10 -0
  10. package/dist/application/ArtifactInventory.js +9 -351
  11. package/dist/application/AuthorizedArtifactInventory.js +2 -2
  12. package/dist/application/BinarySession.js +47 -270
  13. package/dist/application/BinarySessionExecution.js +28 -0
  14. package/dist/application/BinarySessionOpen.js +39 -0
  15. package/dist/application/BinarySessionRecords.js +149 -0
  16. package/dist/application/BinarySessionStatus.js +75 -0
  17. package/dist/application/CapabilityInventory.js +24 -2
  18. package/dist/application/ClientRegistrationStatus.js +1 -1
  19. package/dist/application/CrossVersionInventory.js +8 -2
  20. package/dist/application/CrossVersionInvestigation.js +1 -23
  21. package/dist/application/CrossVersionInvestigationCollections.js +23 -0
  22. package/dist/application/DirectAnalysis.js +47 -44
  23. package/dist/application/Doctor.js +156 -238
  24. package/dist/application/DoctorDiagnostics.js +180 -0
  25. package/dist/application/ElectronBoundaryAnalysis.js +23 -7
  26. package/dist/application/ElectronBoundaryGraphWindows.js +32 -8
  27. package/dist/application/EnhancedToolPagination.js +22 -0
  28. package/dist/application/EnhancedToolTypes.js +4 -0
  29. package/dist/application/EnhancedTools.js +20 -37
  30. package/dist/application/InstrumentedJavaScriptReplayHost.js +36 -0
  31. package/dist/application/InvestigationWorkspaceStore.js +24 -10
  32. package/dist/application/JavaScriptArtifactAnalysis.js +45 -6
  33. package/dist/application/JavaScriptArtifactFiles.js +6 -2
  34. package/dist/application/JavaScriptArtifactGraphBuilder.js +23 -1
  35. package/dist/application/JavaScriptArtifactGraphBundlers.js +225 -0
  36. package/dist/application/JavaScriptArtifactGraphContext.js +37 -36
  37. package/dist/application/JavaScriptArtifactGraphDocuments.js +27 -4
  38. package/dist/application/JavaScriptArtifactGraphFindings.js +54 -8
  39. package/dist/application/JavaScriptArtifactGraphStructure.js +29 -101
  40. package/dist/application/JavaScriptArtifactPathResolution.js +269 -0
  41. package/dist/application/JavaScriptArtifactReconstruction.js +1 -1
  42. package/dist/application/JavaScriptJsonModules.js +39 -0
  43. package/dist/application/JavaScriptModuleRelationships.js +262 -0
  44. package/dist/application/JavaScriptReplayPermission.js +16 -0
  45. package/dist/application/JavaScriptReplayService.js +182 -156
  46. package/dist/application/LinuxHopper.js +10 -0
  47. package/dist/application/MacHopper.js +22 -11
  48. package/dist/application/NodeRuntimeCharacterizationService.js +196 -0
  49. package/dist/application/PermissionAuthority.js +106 -63
  50. package/dist/application/ProcessCaptureCapability.js +1 -1
  51. package/dist/application/ProcessCapturePermission.js +13 -0
  52. package/dist/application/ProcessCli.js +2 -12
  53. package/dist/application/ProcessHarness.js +1 -1
  54. package/dist/application/ProjectPermissionStore.js +48 -9
  55. package/dist/application/RealHopperAssertions.js +76 -0
  56. package/dist/application/ReconstructionCoverageService.js +69 -0
  57. package/dist/application/ReconstructionCoverageWorkspaceStore.js +31 -0
  58. package/dist/application/RuntimeExecutableDiagnostics.js +172 -0
  59. package/dist/application/Setup.js +147 -259
  60. package/dist/application/SetupClientConfiguration.js +220 -0
  61. package/dist/application/SetupClients.js +24 -2
  62. package/dist/application/SetupPlan.js +123 -12
  63. package/dist/application/SetupSkill.js +26 -12
  64. package/dist/application/SupportedClients.js +1 -0
  65. package/dist/application/Uninstall.js +6 -3
  66. package/dist/application/WindowsCapabilities.js +86 -0
  67. package/dist/artifacts/AsarArtifactReader.js +11 -0
  68. package/dist/browser/BrowserProcessStartup.js +88 -0
  69. package/dist/browser/CdpCaptureDocuments.js +12 -8
  70. package/dist/browser/CdpCaptureEventBodyShapes.js +75 -0
  71. package/dist/browser/CdpCaptureEventHandlers.js +336 -0
  72. package/dist/browser/CdpCaptureEventHelpers.js +71 -0
  73. package/dist/browser/CdpCaptureEventState.js +1 -0
  74. package/dist/browser/CdpCaptureEventTypes.js +1 -0
  75. package/dist/browser/CdpCaptureEvents.js +29 -496
  76. package/dist/browser/CdpObservationSession.js +51 -8
  77. package/dist/browser/CdpPageCapture.js +12 -133
  78. package/dist/browser/CdpPageCaptureScripts.js +97 -0
  79. package/dist/browser/CdpPageCaptureWorkers.js +46 -0
  80. package/dist/browser/CdpWebMcpDiscovery.js +38 -14
  81. package/dist/browser/PngVisualDiff.js +9 -2
  82. package/dist/catalogIdentity.js +4 -0
  83. package/dist/cli/artifactCommands.js +82 -0
  84. package/dist/cli/coreAnalysisCommands.js +232 -0
  85. package/dist/cli/managedCommands.js +397 -0
  86. package/dist/cli/managedProjectGraphCommand.js +24 -0
  87. package/dist/cli/options.js +9 -0
  88. package/dist/cli/setupCommands.js +108 -0
  89. package/dist/cli/types.js +1 -0
  90. package/dist/cli/utilityCommands.js +122 -0
  91. package/dist/cli.js +10 -768
  92. package/dist/cliApplicationCommands.js +118 -4
  93. package/dist/cliBrowserAdvancedCommands.js +73 -16
  94. package/dist/cliBrowserCommands.js +39 -115
  95. package/dist/cliCommandNames.js +4 -0
  96. package/dist/cliElectronCommands.js +33 -62
  97. package/dist/cliEvidenceCommands.js +13 -2
  98. package/dist/cliInvestigationCommands.js +53 -10
  99. package/dist/cliObservationOptions.js +137 -0
  100. package/dist/cliPolicyCommands.js +80 -46
  101. package/dist/cliSetup.js +289 -0
  102. package/dist/config/environment.js +107 -0
  103. package/dist/config/parseConfig.js +228 -0
  104. package/dist/config/parsers.js +68 -0
  105. package/dist/config/permissions.js +30 -0
  106. package/dist/config/types.js +1 -0
  107. package/dist/config.js +1 -375
  108. package/dist/contracts/applicationToolContracts.js +134 -0
  109. package/dist/contracts/managedToolContracts.js +1 -1
  110. package/dist/contracts/managedWorkflowToolContracts.js +3 -3
  111. package/dist/contracts/providerSelection.js +2 -0
  112. package/dist/contracts/toolContracts.js +2 -2
  113. package/dist/contracts/toolEffects.js +14 -0
  114. package/dist/contracts/toolOutputSchemaGroups.js +232 -0
  115. package/dist/contracts/toolOutputSchemaPrimitives.js +327 -0
  116. package/dist/contracts/toolOutputSchemas.js +2 -550
  117. package/dist/domain/analysisErrorPresentation.js +234 -0
  118. package/dist/domain/analysisErrorProjection.js +284 -0
  119. package/dist/domain/artifactComparison.js +34 -15
  120. package/dist/domain/binaryTarget.js +87 -37
  121. package/dist/domain/browserObservation.js +1 -285
  122. package/dist/domain/browserObservationSchemas.js +286 -0
  123. package/dist/domain/electronStaticAnalysisBrowser.js +14 -2
  124. package/dist/domain/errors.js +4 -486
  125. package/dist/domain/functionComparison.js +4 -165
  126. package/dist/domain/functionComparisonDimensions.js +164 -0
  127. package/dist/domain/javascriptApplicationVersionItems.js +18 -6
  128. package/dist/domain/javascriptApplicationVersionKeys.js +8 -8
  129. package/dist/domain/javascriptExportInstrumentation.js +97 -0
  130. package/dist/domain/javascriptFeatureTraversal.js +19 -7
  131. package/dist/domain/javascriptRuntimeStaticCandidates.js +3 -1
  132. package/dist/domain/javascriptSemanticAnalysis.js +386 -0
  133. package/dist/domain/javascriptSemanticIr.js +30 -0
  134. package/dist/domain/javascriptSemanticProjection.js +366 -0
  135. package/dist/domain/javascriptSemanticState.js +40 -0
  136. package/dist/domain/javascriptSemanticTraversal.js +22 -0
  137. package/dist/domain/javascriptSemanticValues.js +352 -0
  138. package/dist/domain/javascriptStaticAnalysis.js +5 -221
  139. package/dist/domain/javascriptStaticAnalysisBundler.js +209 -0
  140. package/dist/domain/javascriptStaticAnalysisCalls.js +180 -0
  141. package/dist/domain/javascriptStaticAnalysisHelpers.js +71 -2
  142. package/dist/domain/knownPageCoverage.js +21 -0
  143. package/dist/domain/managedApplicationGraph.js +14 -364
  144. package/dist/domain/managedApplicationGraphCoverage.js +170 -0
  145. package/dist/domain/managedApplicationGraphNodeCoverage.js +6 -0
  146. package/dist/domain/managedApplicationGraphNodes.js +336 -0
  147. package/dist/domain/managedArtifact.js +16 -8
  148. package/dist/domain/managedMemberComparison.js +37 -391
  149. package/dist/domain/managedMemberComparisonCoverage.js +72 -0
  150. package/dist/domain/managedMemberComparisonMatch.js +356 -0
  151. package/dist/domain/managedNativeVerification.js +27 -389
  152. package/dist/domain/managedNativeVerificationMatch.js +272 -0
  153. package/dist/domain/managedNativeVerificationSchemas.js +141 -0
  154. package/dist/domain/managedReconstruction.js +82 -69
  155. package/dist/domain/managedRuntimeCorrelation.js +12 -3
  156. package/dist/domain/nodeRuntimeCharacterization.js +66 -0
  157. package/dist/domain/permissionPolicy.js +2 -0
  158. package/dist/domain/reconstructionCoverage.js +387 -0
  159. package/dist/domain/reconstructionCoverageEvaluation.js +212 -0
  160. package/dist/domain/reconstructionVerificationSchemas.js +8 -0
  161. package/dist/domain/runtimeCharacterization.js +85 -0
  162. package/dist/domain/webBundleAnalyzer.js +3 -409
  163. package/dist/domain/webBundleAnalyzerAst.js +77 -0
  164. package/dist/domain/webBundleAnalyzerInspection.js +307 -0
  165. package/dist/domain/webBundleAnalyzerResult.js +120 -0
  166. package/dist/dotnet/ManagedArtifactInspector.js +50 -29
  167. package/dist/dotnet/ManagedMemberInspector.js +51 -785
  168. package/dist/dotnet/ManagedMemberInspectorCore.js +297 -0
  169. package/dist/dotnet/ManagedMemberInstructionDecoder.js +427 -0
  170. package/dist/dotnet/ManagedMemberRows.js +120 -0
  171. package/dist/dotnet/ManagedMetadataInventory.js +42 -306
  172. package/dist/dotnet/ManagedMetadataInventoryRows.js +292 -0
  173. package/dist/dotnet/ManagedMetadataLayout.js +48 -111
  174. package/dist/dotnet/ManagedMetadataRowSizes.js +54 -0
  175. package/dist/dotnet/ManagedMethodBodyReader.js +143 -0
  176. package/dist/dotnet/ManagedNativeBoundaryHelpers.js +311 -0
  177. package/dist/dotnet/ManagedNativeBoundaryInspector.js +102 -350
  178. package/dist/dotnet/ManagedPeReader.js +59 -28
  179. package/dist/generatedPackageMetadata.js +2 -2
  180. package/dist/ghidra/GhidraClient.js +76 -91
  181. package/dist/ghidra/GhidraClientStartup.js +29 -0
  182. package/dist/ghidra/GhidraClientWire.js +34 -0
  183. package/dist/ghidra/GhidraDefaults.js +1 -1
  184. package/dist/ghidra/GhidraDiagnostics.js +6 -1
  185. package/dist/ghidra/GhidraInstallation.js +20 -15
  186. package/dist/ghidra/GhidraLauncher.js +93 -29
  187. package/dist/ghidra/GhidraProvider.js +61 -161
  188. package/dist/ghidra/GhidraProviderCapabilities.js +173 -0
  189. package/dist/ghidra/GhidraSessionValues.js +2 -0
  190. package/dist/ghidra/GhidraSocketConnection.js +44 -7
  191. package/dist/ghidra/GhidraTargetSnapshot.js +30 -0
  192. package/dist/ghidra/GhidraTransport.js +71 -0
  193. package/dist/identity.js +3 -2
  194. package/dist/main/messages.js +5 -0
  195. package/dist/main/reload.js +49 -0
  196. package/dist/main/shutdown.js +25 -0
  197. package/dist/main/startup.js +14 -0
  198. package/dist/main/state.js +26 -0
  199. package/dist/main/transport.js +61 -0
  200. package/dist/main/types.js +1 -0
  201. package/dist/main.js +35 -146
  202. package/dist/process/PrivateRuntimeRoot.js +3 -2
  203. package/dist/process/ProcessOwnership.js +35 -0
  204. package/dist/process/ProviderProcess.js +2 -0
  205. package/dist/replay/JavaScriptReplayWorker.js +64 -50
  206. package/dist/replay/LinuxJavaScriptReplayRunner.js +173 -390
  207. package/dist/replay/LinuxJavaScriptReplaySandbox.js +140 -0
  208. package/dist/replay/ReplayOutcome.js +95 -0
  209. package/dist/replay/ReplayProcessLifecycle.js +71 -0
  210. package/dist/server/ProcessCaptureElicitation.js +157 -0
  211. package/dist/server/createServer.js +119 -54
  212. package/dist/server/registerApplicationTools/characterization.js +57 -0
  213. package/dist/server/registerApplicationTools/compareVersions.js +34 -0
  214. package/dist/server/registerApplicationTools/controlledReplay.js +38 -0
  215. package/dist/server/registerApplicationTools/coverage.js +84 -0
  216. package/dist/server/registerApplicationTools/helpers.js +42 -0
  217. package/dist/server/registerApplicationTools/traceFeature.js +26 -0
  218. package/dist/server/registerApplicationTools/types.js +1 -0
  219. package/dist/server/registerApplicationTools.js +10 -112
  220. package/dist/server/registerBrowserTools.js +59 -83
  221. package/dist/server/registerElectronTools.js +31 -46
  222. package/dist/server/registerEnhancedTools.js +34 -42
  223. package/dist/server/registerEvidenceResources.js +54 -0
  224. package/dist/server/registerInvestigationTools/helpers.js +74 -0
  225. package/dist/server/registerInvestigationTools/types.js +1 -0
  226. package/dist/server/registerInvestigationTools.js +83 -116
  227. package/dist/server/registerManagedWorkflowTools/compareManagedMembers.js +65 -0
  228. package/dist/server/registerManagedWorkflowTools/contract.js +8 -0
  229. package/dist/server/registerManagedWorkflowTools/evidence.js +43 -0
  230. package/dist/server/registerManagedWorkflowTools/importManagedReconstruction.js +39 -0
  231. package/dist/server/registerManagedWorkflowTools/planManagedRuntimeCorrelation.js +39 -0
  232. package/dist/server/registerManagedWorkflowTools/projectManagedApplicationGraph.js +52 -0
  233. package/dist/server/registerManagedWorkflowTools/types.js +1 -0
  234. package/dist/server/registerManagedWorkflowTools/verifyManagedNativeBoundaries.js +75 -0
  235. package/dist/server/registerManagedWorkflowTools.js +10 -274
  236. package/dist/server/registerOfficialTools.js +73 -59
  237. package/dist/server/registerSessionRecordTools.js +137 -0
  238. package/dist/server/registerSessionStatusTool.js +2 -1
  239. package/dist/server/registerSessionTools.js +60 -164
  240. package/dist/server/sessionAvailabilityPolicy.js +10 -0
  241. package/dist/server/toolRegistrationOptions.js +101 -3
  242. package/dist/server/toolResult.js +5 -0
  243. package/package.json +10 -11
  244. package/skills/{rea-analysis → reverse-engineer-anything}/SKILL.md +20 -7
  245. package/scripts/prepare-node-pty.mjs +0 -35
  246. package/scripts/rebuild-native.mjs +0 -22
@@ -0,0 +1,220 @@
1
+ import { constants as fsConstants } from "node:fs";
2
+ import { copyFile, mkdir, readFile, rm, writeFile } from "node:fs/promises";
3
+ import { dirname } from "node:path";
4
+ import { z } from "zod";
5
+ import writeFileAtomic from "write-file-atomic";
6
+ import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
7
+ import { PRODUCT_IDENTITY } from "../identity.js";
8
+ import { resolveClientConfigTransactionPath } from "./ClientConfigPath.js";
9
+ const defaultCommand = () => [
10
+ "npx",
11
+ "-y",
12
+ PRODUCT_IDENTITY.packageSpecifier,
13
+ "mcp",
14
+ ];
15
+ /** Back up, atomically update, and semantically read back one JSON MCP configuration. */
16
+ export const configureJsonClient = async (client, environment = {}, command = defaultCommand()) => {
17
+ const transactionPath = await resolveClientConfigTransactionPath(client.configPath);
18
+ if (transactionPath === undefined)
19
+ return { status: "failed", reason: "path" };
20
+ let document = {};
21
+ let original;
22
+ try {
23
+ original = await readFile(transactionPath, "utf8");
24
+ document = parseObject(original);
25
+ }
26
+ catch (cause) {
27
+ if (!isMissing(cause))
28
+ return { status: "failed", reason: "readback" };
29
+ }
30
+ let servers;
31
+ try {
32
+ servers = parseOptionalObject(document.mcpServers);
33
+ }
34
+ catch {
35
+ return { status: "failed", reason: "readback" };
36
+ }
37
+ const desired = clientConfigurationDesired(normalizeProviderEnvironment(environment), command);
38
+ if (sameConfiguration(servers[PRODUCT_IDENTITY.mcpServerKey], desired))
39
+ return { status: "unchanged" };
40
+ let backupPath;
41
+ if (original !== undefined) {
42
+ backupPath = `${client.configPath}.rea.backup`;
43
+ if (!(await preserveConfigBackup(transactionPath, backupPath)))
44
+ return { status: "failed", reason: "backup" };
45
+ }
46
+ document.mcpServers = {
47
+ ...servers,
48
+ [PRODUCT_IDENTITY.mcpServerKey]: desired,
49
+ };
50
+ try {
51
+ await mkdir(dirname(client.configPath), { recursive: true });
52
+ await writeFileAtomic(transactionPath, `${JSON.stringify(document, null, 2)}\n`, {
53
+ encoding: "utf8",
54
+ mode: 0o600,
55
+ });
56
+ }
57
+ catch {
58
+ return { status: "failed", reason: "write" };
59
+ }
60
+ try {
61
+ const readback = parseObject(await readFile(transactionPath, "utf8"));
62
+ if (!sameConfiguration(parseOptionalObject(readback.mcpServers)[PRODUCT_IDENTITY.mcpServerKey], desired)) {
63
+ await restoreConfig(transactionPath, original);
64
+ return { status: "failed", reason: "readback" };
65
+ }
66
+ }
67
+ catch {
68
+ await restoreConfig(transactionPath, original);
69
+ return { status: "failed", reason: "readback" };
70
+ }
71
+ return {
72
+ status: "configured",
73
+ ...(backupPath === undefined ? {} : { backupPath }),
74
+ };
75
+ };
76
+ /** Back up, atomically update, and semantically read back Codex TOML configuration. */
77
+ export const configureTomlClient = async (client, environment = {}, command = defaultCommand()) => {
78
+ const transactionPath = await resolveClientConfigTransactionPath(client.configPath);
79
+ if (transactionPath === undefined)
80
+ return { status: "failed", reason: "path" };
81
+ let document = {};
82
+ let original;
83
+ try {
84
+ original = await readFile(transactionPath, "utf8");
85
+ document = objectSchema.parse(parseToml(original));
86
+ }
87
+ catch (cause) {
88
+ if (!isMissing(cause))
89
+ return { status: "failed", reason: "readback" };
90
+ }
91
+ let servers;
92
+ try {
93
+ servers = parseOptionalObject(document.mcp_servers);
94
+ }
95
+ catch {
96
+ return { status: "failed", reason: "readback" };
97
+ }
98
+ const desired = clientConfigurationDesired(normalizeProviderEnvironment(environment), command);
99
+ if (sameConfiguration(servers[PRODUCT_IDENTITY.mcpServerKey], desired))
100
+ return { status: "unchanged" };
101
+ const backupPath = original === undefined ? undefined : `${client.configPath}.rea.backup`;
102
+ if (backupPath !== undefined &&
103
+ !(await preserveConfigBackup(transactionPath, backupPath)))
104
+ return { status: "failed", reason: "backup" };
105
+ document.mcp_servers = {
106
+ ...servers,
107
+ [PRODUCT_IDENTITY.mcpServerKey]: desired,
108
+ };
109
+ try {
110
+ await mkdir(dirname(client.configPath), { recursive: true });
111
+ await writeFileAtomic(transactionPath, stringifyToml(document), {
112
+ encoding: "utf8",
113
+ mode: 0o600,
114
+ });
115
+ const readback = objectSchema.parse(parseToml(await readFile(transactionPath, "utf8")));
116
+ if (!sameConfiguration(parseOptionalObject(readback.mcp_servers)[PRODUCT_IDENTITY.mcpServerKey], desired))
117
+ throw new Error("TOML readback mismatch");
118
+ }
119
+ catch {
120
+ await restoreConfig(transactionPath, original);
121
+ return { status: "failed", reason: "readback" };
122
+ }
123
+ return {
124
+ status: "configured",
125
+ ...(backupPath === undefined ? {} : { backupPath }),
126
+ };
127
+ };
128
+ /** Determine whether an existing client configuration matches the desired registration. */
129
+ export const clientConfigurationAligned = async (client, providerEnvironment, command) => {
130
+ const desired = clientConfigurationDesired(providerEnvironment, command);
131
+ try {
132
+ const original = await readFile(client.configPath, "utf8");
133
+ const document = client.format === "toml"
134
+ ? objectSchema.parse(parseToml(original))
135
+ : parseObject(original);
136
+ const servers = parseOptionalObject(client.format === "toml" ? document.mcp_servers : document.mcpServers);
137
+ return sameConfiguration(servers[PRODUCT_IDENTITY.mcpServerKey], desired);
138
+ }
139
+ catch {
140
+ return false;
141
+ }
142
+ };
143
+ /** Preflight one client configuration before setup presents any mutations. */
144
+ export const inspectClientConfiguration = async (client, providerEnvironment, command) => {
145
+ if (client.format === "unsupported")
146
+ return { status: "already_current" };
147
+ const transactionPath = await resolveClientConfigTransactionPath(client.configPath);
148
+ if (transactionPath === undefined)
149
+ return {
150
+ status: "invalid",
151
+ remediation: "The configuration path is unsafe or unresolved. Check ownership and symbolic links before rerunning setup.",
152
+ };
153
+ let original;
154
+ try {
155
+ original = await readFile(transactionPath, "utf8");
156
+ }
157
+ catch (cause) {
158
+ if (isMissing(cause))
159
+ return { status: "create" };
160
+ return {
161
+ status: "invalid",
162
+ remediation: "The configuration file could not be read. Check its permissions before rerunning setup.",
163
+ };
164
+ }
165
+ const desired = clientConfigurationDesired(providerEnvironment, command);
166
+ try {
167
+ const document = client.format === "toml"
168
+ ? objectSchema.parse(parseToml(original))
169
+ : parseObject(original);
170
+ const servers = parseOptionalObject(client.format === "toml" ? document.mcp_servers : document.mcpServers);
171
+ if (sameConfiguration(servers[PRODUCT_IDENTITY.mcpServerKey], desired))
172
+ return { status: "already_current" };
173
+ }
174
+ catch {
175
+ return {
176
+ status: "invalid",
177
+ remediation: "The existing configuration is malformed. Repair it before setup applies any other changes.",
178
+ };
179
+ }
180
+ return {
181
+ status: "update",
182
+ backupPath: `${client.configPath}.rea.backup`,
183
+ };
184
+ };
185
+ const objectSchema = z.record(z.string(), z.unknown());
186
+ const parseOptionalObject = (value) => value === undefined ? {} : objectSchema.parse(value);
187
+ const parseObject = (text) => objectSchema.parse(JSON.parse(text));
188
+ const isMissing = (cause) => cause instanceof Error && "code" in cause && cause.code === "ENOENT";
189
+ const sameConfiguration = (left, right) => JSON.stringify(left) === JSON.stringify(right);
190
+ const preserveConfigBackup = async (source, destination) => {
191
+ try {
192
+ await copyFile(source, destination, fsConstants.COPYFILE_EXCL);
193
+ return true;
194
+ }
195
+ catch (cause) {
196
+ return cause instanceof Error && "code" in cause && cause.code === "EEXIST";
197
+ }
198
+ };
199
+ const restoreConfig = async (path, original) => {
200
+ try {
201
+ if (original === undefined)
202
+ await rm(path, { force: true });
203
+ else
204
+ await writeFile(path, original, { encoding: "utf8", mode: 0o600 });
205
+ }
206
+ catch {
207
+ // The backup remains available for the remediation reported by setup.
208
+ }
209
+ };
210
+ const clientConfigurationDesired = (providerEnvironment, command) => {
211
+ const environment = Object.fromEntries(Object.entries(providerEnvironment).sort(([left], [right]) => left.localeCompare(right)));
212
+ return {
213
+ command: command[0] ?? PRODUCT_IDENTITY.cliBinary,
214
+ args: command.slice(1),
215
+ ...(Object.keys(environment).length === 0 ? {} : { env: environment }),
216
+ };
217
+ };
218
+ const normalizeProviderEnvironment = (environment) => typeof environment === "string"
219
+ ? { HOPPER_LAUNCHER_PATH: environment }
220
+ : environment;
@@ -11,12 +11,34 @@ const failedConfigurationMessage = (reason) => {
11
11
  export const configureDetectedClients = async (options) => {
12
12
  let failure;
13
13
  for (const client of options.detectedClients) {
14
+ const actionId = `configure_client:${client.name}`;
15
+ const label = client.displayName ?? client.name;
16
+ options.onProgress?.({ actionId, label, state: "started" });
14
17
  const result = await options.host.configureClient(client, options.providerEnvironment, options.command);
15
18
  options.clients[client.name] = result;
16
- if (result.status === "failed")
19
+ if (result.status === "failed") {
17
20
  failure ??= failedConfigurationMessage(result.reason);
18
- else if (result.status === "configured")
21
+ options.onProgress?.({
22
+ actionId,
23
+ label,
24
+ state: "failed",
25
+ detail: failedConfigurationMessage(result.reason),
26
+ });
27
+ }
28
+ else if (result.status === "configured") {
19
29
  options.appliedActions.push(`configured_${client.name}`);
30
+ options.onProgress?.({ actionId, label, state: "completed" });
31
+ }
32
+ else {
33
+ options.onProgress?.({
34
+ actionId,
35
+ label,
36
+ state: "warning",
37
+ detail: result.status === "unchanged"
38
+ ? "Already current"
39
+ : "Manual configuration required",
40
+ });
41
+ }
20
42
  }
21
43
  return failure;
22
44
  };
@@ -1,38 +1,149 @@
1
+ import { homedir } from "node:os";
2
+ import { join } from "node:path";
3
+ import { PRODUCT_IDENTITY } from "../identity.js";
4
+ import { linuxHopperInstallDisclosure, } from "./LinuxHopper.js";
5
+ import { macHopperInstallDisclosure } from "./MacHopper.js";
1
6
  /** Build the complete setup mutation plan before approval. */
2
- export const setupPlan = (platform, installHopper, installSkill, clients, providerEnvironment) => [
3
- ...(installHopper
7
+ const setupPlan = (input) => [
8
+ ...(input.installHopper
4
9
  ? [
5
10
  {
11
+ id: "install_hopper",
6
12
  kind: "install_hopper",
7
- target: platform === "darwin"
8
- ? "~/Applications/Hopper Disassembler.app"
13
+ label: "Hopper deep-analysis provider",
14
+ target: input.platform === "darwin"
15
+ ? join(homedir(), "Applications/Hopper Disassembler.app")
9
16
  : "system package manager",
10
- detail: platform === "linux"
17
+ detail: input.platform === "linux"
11
18
  ? "Download, verify, and install Hopper plus its Xvfb demo-session dependencies. For the supported demo build, REA uses a private display and selects Hopper's offered demo mode for each analysis session."
12
19
  : "Download the official Hopper package, verify it, and install it. Hopper may show its demo or license prompt when first opened.",
13
20
  external: true,
21
+ operation: "install",
22
+ ...(input.platform === "linux"
23
+ ? input.linuxPackageFamily === undefined
24
+ ? {}
25
+ : linuxDisclosure(input.linuxPackageFamily)
26
+ : {
27
+ networkOrigins: macHopperInstallDisclosure.networkOrigins,
28
+ commands: macHopperInstallDisclosure.commands,
29
+ }),
14
30
  },
15
31
  ]
16
32
  : []),
17
- ...clients
18
- .filter(({ format }) => format !== "unsupported")
19
- .map((client) => ({
33
+ ...input.clients
34
+ .filter(({ client }) => client.format !== "unsupported")
35
+ .map(({ client, operation, backupPath }) => ({
36
+ id: `configure_client:${client.name}`,
20
37
  kind: "configure_client",
38
+ label: client.displayName ?? client.name,
21
39
  target: client.configPath,
22
- detail: clientConfigurationDetail(client.name, providerEnvironment),
40
+ detail: clientConfigurationDetail(client.displayName ?? client.name, input.providerEnvironment),
23
41
  external: false,
42
+ operation,
43
+ ...(backupPath === undefined ? {} : { backupPath }),
24
44
  })),
25
- ...(installSkill
45
+ ...(input.installSkill
26
46
  ? [
27
47
  {
48
+ id: "install_skill",
28
49
  kind: "install_skill",
29
- target: "~/.agents/skills/rea-analysis/SKILL.md",
30
- detail: "Install or update the bundled REA analysis skill.",
50
+ label: "REA reverse-engineering skill",
51
+ target: join(homedir(), ".agents/skills", PRODUCT_IDENTITY.skillName, "SKILL.md"),
52
+ detail: "Install or update the bundled REA reverse-engineering skill.",
31
53
  external: false,
54
+ operation: "install",
32
55
  },
33
56
  ]
34
57
  : []),
35
58
  ];
59
+ /** Discover and preflight the complete set of selectable setup actions. */
60
+ export const discoverSetupActions = async (input) => {
61
+ const { host, providerEnvironment, command } = input;
62
+ const initialDoctor = await host.doctor();
63
+ const linuxHopperRepairNeeded = initialDoctor.checks.some(({ name, ok, detail }) => !ok &&
64
+ (name === "hopper-demo-runtime" ||
65
+ (name === "hopper-version" && detail === "/opt/hopper/bin/Hopper")));
66
+ const installHopper = input.forceHopperInstall ||
67
+ (input.proposeHopper &&
68
+ (Object.keys(providerEnvironment).length === 0 ||
69
+ linuxHopperRepairNeeded));
70
+ const [allDetectedClients, skillNeedsInstall] = await Promise.all([
71
+ host.detectedClients(),
72
+ host.skillNeedsInstall(),
73
+ ]);
74
+ const selectedClientIdSet = input.selectedClientIds === undefined
75
+ ? undefined
76
+ : new Set(input.selectedClientIds);
77
+ const detectedClients = allDetectedClients.filter(({ name }) => selectedClientIdSet === undefined || selectedClientIdSet.has(name));
78
+ const installSkill = input.installSkillSelection === false ? false : skillNeedsInstall;
79
+ const clients = installHopper
80
+ ? detectedClients
81
+ : await filterClientsNeedingConfigure({
82
+ host,
83
+ clients: detectedClients,
84
+ providerEnvironment,
85
+ command,
86
+ });
87
+ const inspections = await inspectClients({
88
+ host,
89
+ clients,
90
+ providerEnvironment,
91
+ command,
92
+ });
93
+ const invalid = inspections.find(({ inspection }) => inspection.status === "invalid");
94
+ const blocker = invalid?.inspection.status === "invalid"
95
+ ? `${invalid.client.displayName ?? invalid.client.name}: ${invalid.inspection.remediation}`
96
+ : undefined;
97
+ const clientPlans = inspections.flatMap(({ client, inspection }) => inspection.status === "create" ||
98
+ inspection.status === "update" ||
99
+ (installHopper && inspection.status === "already_current")
100
+ ? [
101
+ {
102
+ client,
103
+ operation: inspection.status === "already_current"
104
+ ? "update"
105
+ : inspection.status,
106
+ ...(inspection.status !== "already_current" &&
107
+ inspection.backupPath !== undefined
108
+ ? { backupPath: inspection.backupPath }
109
+ : {}),
110
+ },
111
+ ]
112
+ : []);
113
+ const linuxDistribution = host.platform === "linux" ? await host.linuxDistribution() : undefined;
114
+ return {
115
+ installHopper,
116
+ installSkill,
117
+ detectedClients: clientPlans.map(({ client }) => client),
118
+ plannedActions: setupPlan({
119
+ platform: host.platform,
120
+ installHopper,
121
+ installSkill,
122
+ clients: clientPlans,
123
+ providerEnvironment,
124
+ ...(linuxDistribution?.packageFamily === undefined
125
+ ? {}
126
+ : { linuxPackageFamily: linuxDistribution.packageFamily }),
127
+ }),
128
+ blocker,
129
+ };
130
+ };
131
+ const inspectClients = async (input) => Promise.all(input.clients.map(async (client) => ({
132
+ client,
133
+ inspection: (await input.host.inspectClientConfiguration?.(client, input.providerEnvironment, input.command)) ?? { status: "update" },
134
+ })));
135
+ const filterClientsNeedingConfigure = async (input) => {
136
+ const needs = await Promise.all(input.clients.map((client) => input.host.clientNeedsConfigure(client, input.providerEnvironment, input.command)));
137
+ return input.clients.filter((_, index) => needs[index]);
138
+ };
139
+ const linuxDisclosure = (family) => {
140
+ const disclosure = linuxHopperInstallDisclosure(family, process.getuid?.() === 0);
141
+ return {
142
+ networkOrigins: [disclosure.downloadUrl],
143
+ commands: disclosure.commands,
144
+ integrity: `${String(disclosure.expectedBytes)} bytes · SHA-1 ${disclosure.expectedSha1}`,
145
+ };
146
+ };
36
147
  const clientConfigurationDetail = (client, environment) => {
37
148
  const entries = Object.entries(environment)
38
149
  .sort(([left], [right]) => left.localeCompare(right))
@@ -2,15 +2,26 @@ import { mkdir, readFile, rm } from "node:fs/promises";
2
2
  import { dirname, join } from "node:path";
3
3
  import writeFileAtomic from "write-file-atomic";
4
4
  import { PRODUCT_IDENTITY } from "../identity.js";
5
+ const skillDestination = (home, name) => join(home, ".agents/skills", name, "SKILL.md");
6
+ const readOptionalText = async (path) => {
7
+ try {
8
+ return await readFile(path, "utf8");
9
+ }
10
+ catch (cause) {
11
+ if (cause instanceof Error && "code" in cause && cause.code === "ENOENT")
12
+ return undefined;
13
+ throw cause;
14
+ }
15
+ };
5
16
  const canonicalSkillFiles = async (home) => ({
6
- destination: join(home, ".agents/skills", PRODUCT_IDENTITY.skillName, "SKILL.md"),
17
+ destination: skillDestination(home, PRODUCT_IDENTITY.skillName),
7
18
  content: await readFile(new URL(`../../skills/${PRODUCT_IDENTITY.skillName}/SKILL.md`, import.meta.url), "utf8"),
8
19
  });
9
20
  /** Report whether setup would change the managed REA skill. */
10
21
  export const canonicalSkillNeedsInstall = async (home) => {
11
22
  try {
12
23
  const { content, destination } = await canonicalSkillFiles(home);
13
- return (await readFile(destination, "utf8")) !== content;
24
+ return (await readOptionalText(destination)) !== content;
14
25
  }
15
26
  catch {
16
27
  return true;
@@ -18,43 +29,46 @@ export const canonicalSkillNeedsInstall = async (home) => {
18
29
  };
19
30
  /** Transactionally install or upgrade the versioned canonical REA skill. */
20
31
  export const installCanonicalSkill = async (home) => {
21
- let destination = join(home, ".agents/skills", PRODUCT_IDENTITY.skillName, "SKILL.md");
32
+ let destination = skillDestination(home, PRODUCT_IDENTITY.skillName);
22
33
  let backup = `${destination}.rea.backup`;
23
34
  let original;
35
+ let canonicalChanged = false;
24
36
  try {
25
37
  const canonical = await canonicalSkillFiles(home);
26
38
  destination = canonical.destination;
27
39
  backup = `${destination}.rea.backup`;
28
40
  const { content } = canonical;
29
- original = await readFile(destination, "utf8").catch(() => undefined);
41
+ original = await readOptionalText(destination);
30
42
  if (original === content)
31
43
  return "unchanged";
32
44
  await mkdir(dirname(destination), { recursive: true });
33
- if (original !== undefined)
45
+ if (original !== undefined && original !== content)
34
46
  await writeFileAtomic(backup, original, {
35
47
  encoding: "utf8",
36
48
  mode: 0o600,
37
49
  });
38
- await writeFileAtomic(destination, content, {
39
- encoding: "utf8",
40
- mode: 0o600,
41
- });
50
+ canonicalChanged = original !== content;
51
+ if (canonicalChanged)
52
+ await writeFileAtomic(destination, content, {
53
+ encoding: "utf8",
54
+ mode: 0o600,
55
+ });
42
56
  if ((await readFile(destination, "utf8")) !== content)
43
57
  throw new Error("skill readback mismatch");
44
58
  return "installed";
45
59
  }
46
60
  catch {
47
61
  try {
48
- if (original === undefined)
62
+ if (canonicalChanged && original === undefined)
49
63
  await rm(destination, { force: true });
50
- else
64
+ else if (canonicalChanged && original !== undefined)
51
65
  await writeFileAtomic(destination, original, {
52
66
  encoding: "utf8",
53
67
  mode: 0o600,
54
68
  });
55
69
  }
56
70
  catch {
57
- // The backup remains beside the skill for explicit operator recovery.
71
+ // The canonical backup remains beside the skill for operator recovery.
58
72
  }
59
73
  return "failed";
60
74
  }
@@ -59,6 +59,7 @@ export const SUPPORTED_CLIENT_DEFINITIONS = [
59
59
  /** Describe every client location that setup, doctor, or uninstall may inspect. */
60
60
  export const supportedClients = (home) => SUPPORTED_CLIENT_DEFINITIONS.map((definition) => ({
61
61
  name: definition.name,
62
+ displayName: definition.displayName,
62
63
  configPath: join(home, ...definition.configPath),
63
64
  markerPath: join(home, ...definition.markerPath),
64
65
  format: definition.format,
@@ -39,7 +39,7 @@ export const runUninstall = async (purgeData, host = systemUninstallHost()) => {
39
39
  export const systemUninstallHost = (home = homedir(), fileSystem = systemFileSystem) => ({
40
40
  clients: () => Promise.resolve(supportedClients(home)),
41
41
  removeClient: (client) => removeClient(client, fileSystem),
42
- removeSkill: () => removeManagedPath(join(home, ".agents/skills", PRODUCT_IDENTITY.skillName), "skill", fileSystem),
42
+ removeSkill: () => removeManagedSkills(home, fileSystem),
43
43
  purgeData: async () => [
44
44
  await removeManagedPath(join(home, ".rea/cache"), "cache", fileSystem),
45
45
  await removeManagedPath(join(home, ".rea/state"), "state", fileSystem),
@@ -127,8 +127,10 @@ const isOwnedRegistration = (value) => {
127
127
  args.length === 1 &&
128
128
  args[0] === "mcp") ||
129
129
  (command === "npx" &&
130
- JSON.stringify(args) ===
131
- JSON.stringify(["-y", PRODUCT_IDENTITY.packageName, "mcp"])));
130
+ (JSON.stringify(args) ===
131
+ JSON.stringify(["-y", PRODUCT_IDENTITY.packageSpecifier, "mcp"]) ||
132
+ JSON.stringify(args) ===
133
+ JSON.stringify(["-y", PRODUCT_IDENTITY.packageName, "mcp"]))));
132
134
  };
133
135
  const removeManagedPath = async (path, name, fileSystem) => {
134
136
  try {
@@ -144,6 +146,7 @@ const removeManagedPath = async (path, name, fileSystem) => {
144
146
  : item(name, "failed", "This item could not be removed. Check file permissions, then rerun uninstall.");
145
147
  }
146
148
  };
149
+ const removeManagedSkills = async (home, fileSystem) => removeManagedPath(join(home, ".agents/skills", PRODUCT_IDENTITY.skillName), "skill", fileSystem);
147
150
  const item = (name, status, detail) => ({ name, status, detail });
148
151
  const objectSchema = z.record(z.string(), z.unknown());
149
152
  const optionalObject = (value) => value === undefined ? {} : objectSchema.parse(value);
@@ -0,0 +1,86 @@
1
+ import { constants } from "node:fs";
2
+ import { mkdtemp, rm, symlink, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { probeProcessCaptureCapability, } from "./ProcessCaptureCapability.js";
6
+ /** Probe named prerequisites without converting missing controls into support. */
7
+ export const probeWindowsCapabilities = async (dependencies = systemDependencies()) => {
8
+ const [symlinkCreation, pty] = await Promise.all([
9
+ dependencies.probeSymlinkCreation(),
10
+ dependencies.probePty(),
11
+ ]);
12
+ return {
13
+ platform: dependencies.platform,
14
+ architecture: dependencies.architecture,
15
+ capabilities: {
16
+ symlink_creation: symlinkCreation,
17
+ no_follow_open: dependencies.probeNoFollowOpen(),
18
+ private_acl: dependencies.probePrivateAcl(),
19
+ unix_domain_socket: dependencies.probeUnixDomainSocket(),
20
+ pty: {
21
+ available: pty.available,
22
+ reason: pty.available ? null : pty.reason,
23
+ },
24
+ },
25
+ };
26
+ };
27
+ const systemDependencies = () => ({
28
+ platform: process.platform,
29
+ architecture: process.arch,
30
+ probeSymlinkCreation,
31
+ probeNoFollowOpen: () => systemNoFollowOpenCapability(process.platform, constants.O_NOFOLLOW),
32
+ probePrivateAcl: () => ({
33
+ available: false,
34
+ reason: "Windows ACL enforcement is not implemented by this REA build",
35
+ }),
36
+ probeUnixDomainSocket: () => process.platform === "win32"
37
+ ? {
38
+ available: false,
39
+ reason: "Node path-based IPC uses named pipes on Windows",
40
+ }
41
+ : { available: true, reason: null },
42
+ probePty: probeProcessCaptureCapability,
43
+ });
44
+ /** Report pathname no-follow support without treating it as Windows authority. */
45
+ export const systemNoFollowOpenCapability = (platform, noFollow) => {
46
+ if (platform === "win32")
47
+ return {
48
+ available: false,
49
+ reason: "Windows reparse-safe handle admission is not implemented",
50
+ };
51
+ return typeof noFollow === "number"
52
+ ? { available: true, reason: null }
53
+ : {
54
+ available: false,
55
+ reason: "O_NOFOLLOW is unavailable in this Node runtime",
56
+ };
57
+ };
58
+ const probeSymlinkCreation = async () => {
59
+ const root = await mkdtemp(join(tmpdir(), "rea-symlink-probe-"));
60
+ const target = join(root, "target");
61
+ const link = join(root, "link");
62
+ try {
63
+ await writeFile(target, "probe");
64
+ await symlink(target, link, "file");
65
+ return { available: true, reason: null };
66
+ }
67
+ catch (cause) {
68
+ return {
69
+ available: false,
70
+ reason: cause instanceof Error &&
71
+ "code" in cause &&
72
+ typeof cause.code === "string"
73
+ ? cause.code
74
+ : "symlink creation failed",
75
+ };
76
+ }
77
+ finally {
78
+ try {
79
+ await rm(root, { recursive: true, force: true });
80
+ }
81
+ catch {
82
+ // Cleanup failure must not replace the machine-readable probe outcome.
83
+ // The probe root lives beneath the OS temporary directory and is unique.
84
+ }
85
+ }
86
+ };
@@ -70,6 +70,13 @@ export class AsarArtifactReader {
70
70
  return Promise.resolve(Readable.from(extractFile(this.path, entry.adapterKey, false)));
71
71
  }
72
72
  catch (cause) {
73
+ if (entry.unpacked && isMissingFile(cause))
74
+ return Promise.reject(new ArtifactReaderFailure("unavailable", `ASAR unpacked entry bytes are unavailable: ${entry.path}`, { cause }, {
75
+ logicalPath: entry.path,
76
+ declaredSha256: entry.declaredSha256,
77
+ calculatedSha256: null,
78
+ unpacked: true,
79
+ }));
73
80
  return Promise.reject(asarFailure(this.path, `read ${entry.path}`, cause));
74
81
  }
75
82
  }
@@ -87,3 +94,7 @@ const abortIfNeeded = (signal) => {
87
94
  const asarFailure = (path, operation, cause) => cause instanceof ArtifactReaderFailure
88
95
  ? cause
89
96
  : new ArtifactReaderFailure("format", `Malformed or unreadable ASAR during ${operation}: ${path}`, { cause });
97
+ const isMissingFile = (cause) => typeof cause === "object" &&
98
+ cause !== null &&
99
+ "code" in cause &&
100
+ cause.code === "ENOENT";