rea-agents 2.4.0 → 3.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 (231) hide show
  1. package/README.md +91 -19
  2. package/bridge/ghidra/ReaGhidraBridge.java +524 -4
  3. package/bridge/hopper_bridge.py +313 -28
  4. package/dist/application/AnalysisClientCleanup.js +14 -0
  5. package/dist/application/AnalysisContextQueries.js +130 -0
  6. package/dist/application/AnalysisSnapshotCache.js +4 -2
  7. package/dist/application/ApplicationWorkflowEvidenceResolver.js +31 -13
  8. package/dist/application/ArtifactInventory/scanCanonical.js +1 -1
  9. package/dist/application/BinarySession.js +20 -8
  10. package/dist/application/BinarySessionComposition.js +21 -0
  11. package/dist/application/BinarySessionRecords.js +79 -9
  12. package/dist/application/BinarySessionStatus.js +31 -1
  13. package/dist/application/BrowserEvidence.js +14 -0
  14. package/dist/application/BrowserScenarioCapturePort.js +1 -0
  15. package/dist/application/BrowserScenarioCaptureService.js +53 -0
  16. package/dist/application/BrowserScenarioEvidence.js +40 -0
  17. package/dist/application/CallPathTracing.js +160 -0
  18. package/dist/application/CapabilityInventory.js +84 -14
  19. package/dist/application/ClientRegistrationStatus.js +4 -1
  20. package/dist/application/CompositeProvider.js +10 -1
  21. package/dist/application/DirectAnalysis.js +13 -4
  22. package/dist/application/EnhancedLiteralTracing.js +118 -0
  23. package/dist/application/EnhancedTools.js +71 -127
  24. package/dist/application/EvidenceLedger.js +45 -16
  25. package/dist/application/JavaScriptApplicationWorkflowEvidence.js +13 -0
  26. package/dist/application/JavaScriptApplicationWorkflowService.js +27 -1
  27. package/dist/application/JavaScriptArtifactAnalysis.js +13 -4
  28. package/dist/application/JavaScriptRuntimeObservationEvidence.js +34 -0
  29. package/dist/application/JavaScriptRuntimeObservationPort.js +1 -0
  30. package/dist/application/JavaScriptRuntimeObservationService.js +46 -0
  31. package/dist/application/JavaScriptSemanticGraphAsyncProjection.js +147 -0
  32. package/dist/application/JavaScriptSemanticGraphBuilder.js +34 -6
  33. package/dist/application/JavaScriptSemanticGraphChildProcessProjection.js +156 -0
  34. package/dist/application/JavaScriptSemanticGraphConstruction.js +2 -2
  35. package/dist/application/JavaScriptSemanticGraphDataProjection.js +203 -0
  36. package/dist/application/JavaScriptSemanticGraphFingerprintProjection.js +25 -0
  37. package/dist/application/JavaScriptSemanticGraphFlowProjection.js +129 -8
  38. package/dist/application/JavaScriptSemanticGraphObjectProjection.js +81 -0
  39. package/dist/application/JavaScriptSemanticGraphProjection.js +14 -7
  40. package/dist/application/JavaScriptSemanticGraphResourceProjection.js +59 -0
  41. package/dist/application/JavaScriptSemanticGraphValueProjection.js +77 -0
  42. package/dist/application/LazyAnalysisProvider.js +77 -0
  43. package/dist/application/LoopbackReplay.js +1 -1
  44. package/dist/application/NativeApiInspection.js +80 -0
  45. package/dist/application/ProcessReactiveEffects.js +6 -0
  46. package/dist/application/ProcessReactiveHarness.js +16 -1
  47. package/dist/application/ProjectPermissionStore.js +1 -0
  48. package/dist/application/ReconstructionObligationAuthority.js +72 -0
  49. package/dist/application/ReconstructionObligationCandidates.js +265 -0
  50. package/dist/application/ReconstructionObligationLedgerDiagnostics.js +47 -0
  51. package/dist/application/ReconstructionObligationLedgerEvaluation.js +263 -0
  52. package/dist/application/ReconstructionObligationLedgerProjection.js +86 -0
  53. package/dist/application/ReconstructionObligationLedgerService.js +72 -0
  54. package/dist/application/ReconstructionObligationOriginalCases.js +33 -0
  55. package/dist/application/ReconstructionObligationPolicies.js +132 -0
  56. package/dist/application/ReconstructionReadinessService.js +48 -0
  57. package/dist/application/SessionProviderRouter.js +6 -6
  58. package/dist/application/SetupClientConfiguration.js +4 -2
  59. package/dist/application/runtime.js +26 -9
  60. package/dist/artifacts/ArtifactProvider.js +87 -22
  61. package/dist/browser/BrowserScenarioSecrets.js +53 -0
  62. package/dist/browser/BrowserScenarioSessionPort.js +1 -0
  63. package/dist/browser/CdpBrowserProvider.js +2 -2
  64. package/dist/browser/CdpCaptureDocuments.js +57 -0
  65. package/dist/browser/CdpCaptureStorage.js +71 -29
  66. package/dist/browser/CdpCaptureStorageFingerprints.js +328 -0
  67. package/dist/browser/CdpEndpoint.js +8 -6
  68. package/dist/browser/CdpPageCapture.js +2 -0
  69. package/dist/browser/JavaScriptRuntimeScope.js +38 -0
  70. package/dist/browser/PlaywrightBrowserScenarioProvider.js +259 -0
  71. package/dist/browser/PlaywrightExecutionBoundary.js +42 -0
  72. package/dist/browser/PlaywrightScenarioActions.js +62 -0
  73. package/dist/browser/PlaywrightScenarioArtifacts.js +238 -0
  74. package/dist/browser/PlaywrightScenarioBrowser.js +144 -0
  75. package/dist/browser/PlaywrightScenarioEvents.js +301 -0
  76. package/dist/browser/PlaywrightScenarioSession.js +300 -0
  77. package/dist/browser/V8InspectorCaptureProjection.js +110 -0
  78. package/dist/browser/V8InspectorEndpoint.js +72 -0
  79. package/dist/browser/V8InspectorProvider.js +267 -0
  80. package/dist/catalogIdentity.js +11 -3
  81. package/dist/cli/artifactCommands.js +44 -3
  82. package/dist/cli/coreAnalysisCommands.js +78 -0
  83. package/dist/cli.js +4 -0
  84. package/dist/cliApplicationCommands.js +30 -2
  85. package/dist/cliBrowserAdvancedCommands.js +17 -5
  86. package/dist/cliBrowserCommands.js +1 -0
  87. package/dist/cliBrowserScenarioCommands.js +43 -0
  88. package/dist/cliCommandNames.js +9 -0
  89. package/dist/cliJavaScriptRuntimeCommands.js +164 -0
  90. package/dist/cliObservationOptions.js +4 -0
  91. package/dist/cliPolicyCommands.js +1 -0
  92. package/dist/config/environment.js +12 -0
  93. package/dist/config/parseConfig.js +88 -17
  94. package/dist/contracts/analysisSearchContract.js +14 -0
  95. package/dist/contracts/applicationToolContracts.js +86 -2
  96. package/dist/contracts/applicationWorkflowInputContracts.js +18 -0
  97. package/dist/contracts/artifactToolContracts.js +29 -2
  98. package/dist/contracts/browserProviderToolContracts.js +7 -0
  99. package/dist/contracts/browserScenarioToolContracts.js +81 -0
  100. package/dist/contracts/browserToolContracts.js +88 -98
  101. package/dist/contracts/enhancedInputs.js +35 -6
  102. package/dist/contracts/functionInstructionContract.js +8 -0
  103. package/dist/contracts/functionWorkflowToolContracts.js +30 -0
  104. package/dist/contracts/hopperMemoryContracts.js +22 -0
  105. package/dist/contracts/javascriptApplicationWorkflowExamples.js +42 -0
  106. package/dist/contracts/javascriptRuntimeObservationToolContracts.js +58 -0
  107. package/dist/contracts/promptContracts.js +14 -15
  108. package/dist/contracts/reconstructionReadinessExample.js +436 -0
  109. package/dist/contracts/toolContractExamples.js +8 -0
  110. package/dist/contracts/toolContractTypes.js +1 -0
  111. package/dist/contracts/toolContracts.js +34 -25
  112. package/dist/contracts/toolEffects.js +25 -0
  113. package/dist/contracts/toolOutputSchemaGroups.js +120 -30
  114. package/dist/contracts/toolOutputSchemaPrimitives.js +37 -0
  115. package/dist/domain/analysisErrorPresentation.js +8 -2
  116. package/dist/domain/analysisErrorProjection.js +9 -0
  117. package/dist/domain/artifactInspection.js +347 -0
  118. package/dist/domain/browserCaptureComparison.js +82 -0
  119. package/dist/domain/browserObservation.js +8 -1
  120. package/dist/domain/browserObservationErrors.js +1 -0
  121. package/dist/domain/browserObservationSchemas.js +26 -0
  122. package/dist/domain/browserScenario.js +165 -0
  123. package/dist/domain/browserScenarioCapture.js +57 -0
  124. package/dist/domain/browserScenarioCaptureValues.js +204 -0
  125. package/dist/domain/browserScenarioDiff.js +324 -0
  126. package/dist/domain/browserScenarioDiffValues.js +157 -0
  127. package/dist/domain/browserScenarioNormalization.js +45 -0
  128. package/dist/domain/browserScenarioValues.js +372 -0
  129. package/dist/domain/bytecodeProvider.js +179 -0
  130. package/dist/domain/conformancePackage.js +169 -0
  131. package/dist/domain/conformanceReplay.js +104 -0
  132. package/dist/domain/conformanceTrustGate.js +193 -0
  133. package/dist/domain/customProtocolCapture.js +137 -0
  134. package/dist/domain/eventProcessTree.js +214 -0
  135. package/dist/domain/evidenceBundle.js +47 -0
  136. package/dist/domain/hopperStartupFailure.js +1 -1
  137. package/dist/domain/hopperValues.js +14 -1
  138. package/dist/domain/inputIssueProjection.js +7 -1
  139. package/dist/domain/javascriptRuntimeObservation.js +170 -0
  140. package/dist/domain/javascriptRuntimeReconciliationParsing.js +120 -2
  141. package/dist/domain/javascriptRuntimeReconciliationResult.js +3 -3
  142. package/dist/domain/javascriptRuntimeReconciliationRuntime.js +2 -2
  143. package/dist/domain/javascriptRuntimeReconciliationSchemas.js +1 -1
  144. package/dist/domain/javascriptSemanticAnalysis.js +13 -16
  145. package/dist/domain/javascriptSemanticAsyncEffects.js +253 -0
  146. package/dist/domain/javascriptSemanticChildProcesses.js +237 -0
  147. package/dist/domain/javascriptSemanticDataEffectHelpers.js +125 -0
  148. package/dist/domain/javascriptSemanticDataEffects.js +284 -0
  149. package/dist/domain/javascriptSemanticDerivedAnalysis.js +41 -0
  150. package/dist/domain/javascriptSemanticFingerprints.js +174 -0
  151. package/dist/domain/javascriptSemanticIr.js +21 -1
  152. package/dist/domain/javascriptSemanticObjects.js +115 -0
  153. package/dist/domain/javascriptSemanticPromises.js +290 -0
  154. package/dist/domain/javascriptSemanticResources.js +136 -0
  155. package/dist/domain/javascriptSourceParser.js +15 -0
  156. package/dist/domain/javascriptStaticAnalysis.js +12 -15
  157. package/dist/domain/javascriptStaticAnalysisBundler.js +38 -0
  158. package/dist/domain/javascriptStaticAnalysisCalls.js +23 -1
  159. package/dist/domain/javascriptStaticAnalysisHelpers.js +8 -0
  160. package/dist/domain/mobileApplicationInvestigation.js +159 -0
  161. package/dist/domain/nativeApiBoundary.js +130 -0
  162. package/dist/domain/objcSwiftMetadata.js +150 -0
  163. package/dist/domain/packageAnalysis.js +123 -0
  164. package/dist/domain/peInspection.js +196 -0
  165. package/dist/domain/processCapture.js +8 -2
  166. package/dist/domain/processReactiveScenario.js +1 -0
  167. package/dist/domain/processReactiveTransition.js +9 -4
  168. package/dist/domain/protocolCapture.js +243 -0
  169. package/dist/domain/providerCleanupError.js +11 -0
  170. package/dist/domain/reconstructionObligationLedgerSchemas.js +278 -0
  171. package/dist/domain/reconstructionReadiness.js +121 -0
  172. package/dist/domain/reconstructionReadinessFindingHelpers.js +17 -0
  173. package/dist/domain/reconstructionReadinessIntegrityFindings.js +118 -0
  174. package/dist/domain/reconstructionReadinessJourneyFindings.js +193 -0
  175. package/dist/domain/reconstructionReadinessSchemas.js +240 -0
  176. package/dist/domain/sourceToBundleComparison.js +335 -0
  177. package/dist/domain/sourceToBundleComparisonSchemas.js +133 -0
  178. package/dist/domain/sourceToBundleSignals.js +202 -0
  179. package/dist/domain/webCaptureDiff.js +141 -27
  180. package/dist/domain/webScreenshot.js +1 -1
  181. package/dist/dotnet/ManagedStaticProvider.js +34 -5
  182. package/dist/generatedMcpToolCatalog.js +37 -0
  183. package/dist/generatedPackageMetadata.js +4 -4
  184. package/dist/ghidra/GhidraDefaults.js +1 -1
  185. package/dist/ghidra/GhidraFunctionValues.js +40 -2
  186. package/dist/ghidra/GhidraProviderCapabilities.js +47 -38
  187. package/dist/hopper/HopperCleanup.js +158 -0
  188. package/dist/hopper/HopperClient.js +93 -99
  189. package/dist/hopper/HopperProvider.js +61 -30
  190. package/dist/hopper/HopperRequestQueue.js +215 -0
  191. package/dist/hopper/HopperResponseStream.js +46 -0
  192. package/dist/hopper/protocol.js +63 -20
  193. package/dist/main/transport.js +24 -27
  194. package/dist/main.js +14 -6
  195. package/dist/mcpDoctor.js +27 -4
  196. package/dist/mcpStartupPolicy.js +14 -0
  197. package/dist/process/ProcessCommandIdentity.js +19 -0
  198. package/dist/process/ProcessOwnership.js +11 -8
  199. package/dist/server/DynamicToolAvailability.js +70 -0
  200. package/dist/server/LazyToolCatalog.js +134 -0
  201. package/dist/server/createServer.js +92 -154
  202. package/dist/server/hydrateServerTools.js +191 -0
  203. package/dist/server/lifecycleProgress.js +10 -0
  204. package/dist/server/permissionFailure.js +12 -0
  205. package/dist/server/registerApplicationTools/compareSourceToBundle.js +35 -0
  206. package/dist/server/registerApplicationTools/helpers.js +11 -7
  207. package/dist/server/registerApplicationTools/obligationLedger.js +27 -0
  208. package/dist/server/registerApplicationTools/readiness.js +55 -0
  209. package/dist/server/registerApplicationTools.js +6 -0
  210. package/dist/server/registerArtifactTools.js +9 -1
  211. package/dist/server/registerBrowserScenarioTool.js +25 -0
  212. package/dist/server/registerBrowserTools.js +2 -2
  213. package/dist/server/registerCloseLifecycleTool.js +57 -0
  214. package/dist/server/registerEnhancedTools.js +28 -7
  215. package/dist/server/registerEvidenceResources.js +54 -101
  216. package/dist/server/registerEvidenceTools.js +9 -2
  217. package/dist/server/registerInvestigationTools.js +1 -2
  218. package/dist/server/registerJavaScriptApplicationGraphResource.js +0 -4
  219. package/dist/server/registerJavaScriptRuntimeObservationTools.js +37 -0
  220. package/dist/server/registerReconstructionObligationLedgerResource.js +32 -0
  221. package/dist/server/registerReconstructionReadinessResource.js +32 -0
  222. package/dist/server/registerSessionRecordTools.js +82 -12
  223. package/dist/server/registerSessionStatusTool.js +11 -8
  224. package/dist/server/registerSessionTools.js +32 -58
  225. package/dist/server/sessionAvailabilityPolicy.js +10 -8
  226. package/dist/server/toolRegistrationOptions.js +21 -6
  227. package/dist/server/toolResult.js +12 -0
  228. package/package.json +28 -11
  229. package/scripts/package-runner-bootstrap.mjs +72 -0
  230. package/scripts/rea.mjs +11 -1
  231. package/skills/reverse-engineer-anything/SKILL.md +10 -3
@@ -0,0 +1,160 @@
1
+ import { projectAnalysisError } from "../domain/errors.js";
2
+ import { parseRelatedAddresses } from "../domain/hopperValues.js";
3
+ import { ok } from "../domain/result.js";
4
+ /** Trace one deterministic bounded direct caller/callee path. */
5
+ export const traceCallPath = async (call, input, signal) => {
6
+ const state = createTraceState(input);
7
+ while (state.queueIndex < state.queue.length &&
8
+ state.traversalPath.length === 0) {
9
+ const address = state.queue[state.queueIndex++];
10
+ if (address === undefined)
11
+ break;
12
+ const current = state.visited.get(address);
13
+ if (current === undefined)
14
+ continue;
15
+ if (current.depth >= input.max_depth) {
16
+ state.frontier.add(address);
17
+ state.residual.add("Call-path traversal reached the depth limit.");
18
+ continue;
19
+ }
20
+ if (state.operations >= input.max_operations) {
21
+ recordPendingFrontier(state, input);
22
+ state.residual.add("Call-path traversal reached the operation budget.");
23
+ break;
24
+ }
25
+ state.operations += 1;
26
+ const found = await expandAddress(call, {
27
+ input,
28
+ state,
29
+ address,
30
+ current,
31
+ ...(signal === undefined ? {} : { signal }),
32
+ });
33
+ if (found)
34
+ break;
35
+ }
36
+ return ok(projectTrace(input, state));
37
+ };
38
+ const createTraceState = (input) => ({
39
+ visited: new Map([[input.start, { depth: 0, path: [input.start] }]]),
40
+ queue: [input.start],
41
+ edges: new Map(),
42
+ failures: [],
43
+ frontier: new Set(),
44
+ residual: new Set(),
45
+ operations: 0,
46
+ queueIndex: 0,
47
+ traversalPath: input.goal === input.start ? [input.start] : [],
48
+ });
49
+ const expandAddress = async (call, expansion) => {
50
+ const { input, state, address, current, signal } = expansion;
51
+ const relation = input.direction === "forward" ? "callees" : "callers";
52
+ const tool = input.direction === "forward" ? "procedure_callees" : "procedure_callers";
53
+ const result = await call(tool, { procedure: address }, signal);
54
+ if (!result.ok) {
55
+ state.failures.push({
56
+ address,
57
+ error: projectAnalysisError(result.error),
58
+ });
59
+ state.residual.add(`Call relationships were unavailable for ${address}.`);
60
+ return false;
61
+ }
62
+ const related = parseRelatedAddresses(result.value, relation);
63
+ if (!related.ok) {
64
+ state.failures.push({
65
+ address,
66
+ error: projectAnalysisError(related.error),
67
+ });
68
+ state.residual.add(`Call relationships were unreadable for ${address}.`);
69
+ return false;
70
+ }
71
+ for (const relatedAddress of sortedUniqueAddresses(related.value)) {
72
+ recordEdge(input, state, {
73
+ address,
74
+ relatedAddress,
75
+ discoveryDepth: current.depth + 1,
76
+ });
77
+ const path = state.visited.get(relatedAddress)?.path ?? [
78
+ ...current.path,
79
+ relatedAddress,
80
+ ];
81
+ if (state.visited.has(relatedAddress)) {
82
+ if (relatedAddress === input.goal) {
83
+ state.traversalPath = path;
84
+ return true;
85
+ }
86
+ continue;
87
+ }
88
+ if (state.visited.size >= input.max_nodes) {
89
+ if (state.frontier.size < input.max_nodes)
90
+ state.frontier.add(relatedAddress);
91
+ state.residual.add("Call-path traversal reached the node limit.");
92
+ continue;
93
+ }
94
+ state.visited.set(relatedAddress, {
95
+ depth: current.depth + 1,
96
+ path,
97
+ });
98
+ if (relatedAddress === input.goal) {
99
+ state.traversalPath = path;
100
+ return true;
101
+ }
102
+ state.queue.push(relatedAddress);
103
+ }
104
+ return false;
105
+ };
106
+ const recordEdge = (input, state, edge) => {
107
+ const sourceAddress = input.direction === "forward" ? edge.address : edge.relatedAddress;
108
+ const targetAddress = input.direction === "forward" ? edge.relatedAddress : edge.address;
109
+ const edgeKey = `${sourceAddress}\u0000${targetAddress}`;
110
+ if (state.edges.has(edgeKey))
111
+ return;
112
+ if (state.edges.size >= input.max_nodes) {
113
+ state.residual.add("Call-path traversal reached the edge limit.");
114
+ return;
115
+ }
116
+ state.edges.set(edgeKey, {
117
+ source_address: sourceAddress,
118
+ target_address: targetAddress,
119
+ discovery_depth: edge.discoveryDepth,
120
+ });
121
+ };
122
+ const recordPendingFrontier = (state, input) => {
123
+ for (const pending of state.queue.slice(state.queueIndex - 1))
124
+ if (state.frontier.size < input.max_nodes)
125
+ state.frontier.add(pending);
126
+ };
127
+ const projectTrace = (input, state) => ({
128
+ start: input.start,
129
+ goal: input.goal ?? null,
130
+ direction: input.direction,
131
+ goal_status: input.goal === undefined
132
+ ? "not_requested"
133
+ : state.traversalPath.length > 0
134
+ ? "reached"
135
+ : "not_reached",
136
+ nodes: [...state.visited.entries()]
137
+ .map(([address, { depth }]) => ({ address, depth }))
138
+ .sort((left, right) => left.depth - right.depth || compareAddress(left.address, right.address)),
139
+ edges: [...state.edges.values()].sort((left, right) => left.discovery_depth - right.discovery_depth ||
140
+ compareAddress(left.source_address, right.source_address) ||
141
+ compareAddress(left.target_address, right.target_address)),
142
+ traversal_path: state.traversalPath,
143
+ failures: state.failures,
144
+ frontier: [...state.frontier].sort(compareAddress),
145
+ limits: {
146
+ max_depth: input.max_depth,
147
+ max_nodes: input.max_nodes,
148
+ max_operations: input.max_operations,
149
+ nodes_visited: state.visited.size,
150
+ operations_used: state.operations,
151
+ },
152
+ truncated: state.residual.size > 0,
153
+ residual_unknowns: [...state.residual],
154
+ limitations: [
155
+ "Direct provider call relationships may omit unresolved indirect calls.",
156
+ "A not_reached goal is only absent from this bounded traversal, not from the complete program.",
157
+ ],
158
+ });
159
+ const compareAddress = (left, right) => left < right ? -1 : left > right ? 1 : 0;
160
+ const sortedUniqueAddresses = (addresses) => [...new Set(addresses)].sort(compareAddress);
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { TOOL_CONTRACTS } from "../contracts/toolContracts.js";
2
+ import { GENERATED_MCP_TOOL_CATALOG } from "../generatedMcpToolCatalog.js";
3
3
  const statusSchema = z.object({
4
4
  open: z.boolean(),
5
5
  kind: z.enum(["executable", "database", "archive", "artifact"]).optional(),
@@ -11,6 +11,18 @@ const statusSchema = z.object({
11
11
  availability_code: z.string().nullable().optional(),
12
12
  })),
13
13
  });
14
+ const NO_CLIENT_FEATURES = {
15
+ elicitation_form: false,
16
+ elicitation_url: false,
17
+ roots: false,
18
+ sampling: false,
19
+ };
20
+ const CLIENT_FEATURE_REQUIREMENTS = {
21
+ capture_process_scenario: {
22
+ required: [],
23
+ optional: ["elicitation_form"],
24
+ },
25
+ };
14
26
  const ENHANCED_REQUIREMENTS = {
15
27
  swift_classes: ["list_procedures"],
16
28
  get_objc_classes: ["list_names"],
@@ -26,6 +38,7 @@ const ENHANCED_REQUIREMENTS = {
26
38
  "list_strings",
27
39
  ],
28
40
  analyze_function: ["analyze_function"],
41
+ inspect_native_api: ["analyze_function"],
29
42
  trace_feature: [
30
43
  "list_strings",
31
44
  "list_procedures",
@@ -33,8 +46,15 @@ const ENHANCED_REQUIREMENTS = {
33
46
  "resolve_containing_procedure",
34
47
  ],
35
48
  };
36
- /** Build stable per-operation availability without hiding familiar tools. */
37
- export const buildCapabilityInventory = (sessionStatus, policy) => {
49
+ const SESSION_COMPOSITION_REQUIREMENTS = {
50
+ get_navigation_context: [
51
+ "current_document",
52
+ "current_address",
53
+ "resolve_containing_procedure",
54
+ ],
55
+ };
56
+ /** Build stable per-operation availability for discovery and tool visibility. */
57
+ export const buildCapabilityInventory = (sessionStatus, policy, clientFeatures = NO_CLIENT_FEATURES) => {
38
58
  const status = statusSchema.parse(sessionStatus);
39
59
  const descriptors = new Map(status.capabilities.map((descriptor) => [
40
60
  descriptor.operation,
@@ -44,7 +64,7 @@ export const buildCapabilityInventory = (sessionStatus, policy) => {
44
64
  availability_code: descriptor.availability_code ?? null,
45
65
  },
46
66
  ]));
47
- return TOOL_CONTRACTS.map((contract) => {
67
+ return GENERATED_MCP_TOOL_CATALOG.map((contract) => {
48
68
  const availability = availabilityFor({
49
69
  name: contract.name,
50
70
  kind: contract.kind,
@@ -53,12 +73,19 @@ export const buildCapabilityInventory = (sessionStatus, policy) => {
53
73
  descriptors,
54
74
  policy,
55
75
  });
76
+ const clientRequirements = clientRequirementsFor(contract.name, clientFeatures);
77
+ const clientBlocked = clientRequirements.missing_required.length > 0;
56
78
  return {
57
79
  name: contract.name,
58
80
  surface: contract.kind,
59
- available: availability.reason === "available",
60
- reason: availability.reason,
61
- remediation: availability.remediation,
81
+ available: availability.reason === "available" && !clientBlocked,
82
+ reason: clientBlocked
83
+ ? "client_capability_missing"
84
+ : availability.reason,
85
+ remediation: clientBlocked
86
+ ? `Use an MCP client that supports: ${clientRequirements.missing_required.join(", ")}.`
87
+ : availability.remediation,
88
+ client_requirements: clientRequirements,
62
89
  effects: { ...contract.effects },
63
90
  annotations: {
64
91
  read_only: contract.annotations.readOnlyHint ?? false,
@@ -69,7 +96,22 @@ export const buildCapabilityInventory = (sessionStatus, policy) => {
69
96
  };
70
97
  }).sort((left, right) => left.name.localeCompare(right.name));
71
98
  };
99
+ const clientRequirementsFor = (name, clientFeatures) => {
100
+ const requirements = CLIENT_FEATURE_REQUIREMENTS[name] ?? {
101
+ required: [],
102
+ optional: [],
103
+ };
104
+ return {
105
+ required: [...requirements.required],
106
+ optional: [...requirements.optional],
107
+ missing_required: requirements.required.filter((feature) => !clientFeatures[feature]),
108
+ missing_optional: requirements.optional.filter((feature) => !clientFeatures[feature]),
109
+ };
110
+ };
72
111
  const availabilityFor = (context) => {
112
+ const sessionComposition = SESSION_COMPOSITION_REQUIREMENTS[context.name];
113
+ if (sessionComposition !== undefined)
114
+ return composedAvailabilityFor(sessionComposition, context.descriptors);
73
115
  const javascriptApplication = javascriptApplicationAvailability(context);
74
116
  if (javascriptApplication !== null)
75
117
  return javascriptApplication;
@@ -94,6 +136,9 @@ const javascriptApplicationAvailability = ({ name, policy, }) => {
94
136
  : null;
95
137
  };
96
138
  const policyAvailability = ({ name, kind, policy, }) => {
139
+ const browser = browserPolicyAvailability(name, kind, policy);
140
+ if (browser !== null)
141
+ return browser;
97
142
  if (name === "capture_process_scenario" && !policy.processCaptureEnabled)
98
143
  return {
99
144
  reason: "policy_disabled",
@@ -121,26 +166,43 @@ const policyAvailability = ({ name, kind, policy, }) => {
121
166
  reason: "policy_disabled",
122
167
  remediation: "Configure or grant an evidence_read root.",
123
168
  };
124
- if (kind === "browser-provider")
125
- return policy.browserObservationEnabled === true
169
+ if (kind === "electron-provider")
170
+ return policy.electronObservationEnabled === true
126
171
  ? { reason: "available", remediation: null }
127
172
  : {
128
173
  reason: "policy_disabled",
129
- remediation: "Enable browser observation and configure exact CDP endpoint and page origins.",
174
+ remediation: "Enable Electron observation and configure a loopback CDP endpoint and canonical file roots.",
130
175
  };
131
- if (kind === "electron-provider")
132
- return policy.electronObservationEnabled === true
176
+ if (kind === "runtime-provider")
177
+ return policy.v8InspectorObservationEnabled === true
133
178
  ? { reason: "available", remediation: null }
134
179
  : {
135
180
  reason: "policy_disabled",
136
- remediation: "Enable Electron observation and configure a loopback CDP endpoint and canonical file roots.",
181
+ remediation: "Enable V8 Inspector observation and configure exact loopback endpoints plus canonical file roots or exact origins.",
137
182
  };
138
183
  if (kind === "session")
139
184
  return { reason: "available", remediation: null };
140
185
  return null;
141
186
  };
187
+ const browserPolicyAvailability = (name, kind, policy) => {
188
+ if (name === "capture_browser_scenario")
189
+ return policy.browserScenarioEnabled === true
190
+ ? { reason: "available", remediation: null }
191
+ : {
192
+ reason: "policy_disabled",
193
+ remediation: "Enable browser scenarios and configure exact executable roots or loopback CDP endpoints, page origins, and environment secret names.",
194
+ };
195
+ if (kind !== "browser-provider")
196
+ return null;
197
+ return policy.browserObservationEnabled === true
198
+ ? { reason: "available", remediation: null }
199
+ : {
200
+ reason: "policy_disabled",
201
+ remediation: "Enable browser observation and configure exact CDP endpoint and page origins.",
202
+ };
203
+ };
142
204
  const targetAvailability = ({ kind, targetKind, targetOpen, }) => {
143
- if (!targetOpen)
205
+ if (!targetOpen && (kind === "official-proxy" || kind === "enhanced"))
144
206
  return {
145
207
  reason: "target_required",
146
208
  remediation: "Call open_binary with a supported local target.",
@@ -159,6 +221,11 @@ const providerAvailability = ({ descriptors, kind, name, }) => {
159
221
  if (kind === "enhanced")
160
222
  return composedAvailability(name, descriptors);
161
223
  const descriptor = descriptors.get(name);
224
+ if (descriptor === undefined &&
225
+ (kind === "artifact-provider" ||
226
+ kind === "managed-provider" ||
227
+ kind === "native-provider"))
228
+ return { reason: "available", remediation: null };
162
229
  if (descriptor === undefined)
163
230
  return {
164
231
  reason: "provider_missing",
@@ -184,6 +251,9 @@ const composedAvailability = (name, descriptors) => {
184
251
  reason: "provider_missing",
185
252
  remediation: "No provider composition is declared for this operation.",
186
253
  };
254
+ return composedAvailabilityFor(requirements, descriptors);
255
+ };
256
+ const composedAvailabilityFor = (requirements, descriptors) => {
187
257
  const missing = requirements.find((operation) => !descriptors.has(operation));
188
258
  if (missing !== undefined)
189
259
  return {
@@ -3,6 +3,7 @@ import { resolve } from "node:path";
3
3
  import { parse as parseToml } from "smol-toml";
4
4
  import { z } from "zod";
5
5
  import { PRODUCT_IDENTITY } from "../identity.js";
6
+ import { MCP_STARTUP_POLICY } from "../mcpStartupPolicy.js";
6
7
  import { supportedClients } from "./SupportedClients.js";
7
8
  const objectSchema = z.record(z.string(), z.unknown());
8
9
  const registrationSchema = z
@@ -41,7 +42,9 @@ export const readClientRegistrationStatuses = async (home, currentCommandPath =
41
42
  };
42
43
  const registrationAligned = (registration, client, currentCommandPath) => {
43
44
  const command = [registration.command, ...registration.args];
44
- if (client === "codex" && registration.startup_timeout_sec !== 30)
45
+ if (client === "codex" &&
46
+ registration.startup_timeout_sec !==
47
+ MCP_STARTUP_POLICY.codexStartupTimeoutSeconds)
45
48
  return false;
46
49
  if (command.length === 4 &&
47
50
  command[0] === "npx" &&
@@ -1,5 +1,6 @@
1
1
  import { AnalysisCapabilityUnavailableError } from "../domain/errors.js";
2
2
  import { err, ok } from "../domain/result.js";
3
+ import { closeAnalysisClient } from "./AnalysisClientCleanup.js";
3
4
  /** Synthetic compatibility identity for a deterministic provider set. */
4
5
  export const compositeProviderIdentity = (identities) => ({
5
6
  id: `composite:${identities
@@ -61,6 +62,10 @@ export class CompositeProvider {
61
62
  clients.set(provider, created);
62
63
  return created;
63
64
  };
65
+ const closeWithOutcome = async (options) => {
66
+ const outcomes = await Promise.all([...clients.entries()].map(([provider, client]) => closeAnalysisClient(client, provider.identity().id, options)));
67
+ return outcomes.find((outcome) => !outcome.ok) ?? ok(null);
68
+ };
64
69
  return {
65
70
  execute: (operation, parameters, options) => {
66
71
  if (operation === "health")
@@ -80,8 +85,12 @@ export class CompositeProvider {
80
85
  runtimeLineageSnapshots: () => [...clients.values()]
81
86
  .flatMap((client) => client.runtimeLineageSnapshots?.() ?? [])
82
87
  .sort((left, right) => left.provider.id.localeCompare(right.provider.id)),
88
+ requestActivitySnapshots: () => [...clients.values()]
89
+ .flatMap((client) => client.requestActivitySnapshots?.() ?? [])
90
+ .sort((left, right) => left.provider.id.localeCompare(right.provider.id)),
91
+ closeWithOutcome,
83
92
  close: async () => {
84
- await Promise.allSettled([...clients.values()].map(async (client) => client.close()));
93
+ await closeWithOutcome();
85
94
  },
86
95
  };
87
96
  }
@@ -12,6 +12,7 @@ import { err, ok } from "../domain/result.js";
12
12
  import { loadConfiguredPermissionAuthority } from "./PermissionConfiguration.js";
13
13
  import { authorizeFileReadWithDeferredWrite, } from "./DeferredFileAuthorization.js";
14
14
  import { REA_WORKFLOW_PROVIDER, workflowAnalysisProfile, } from "./InvestigationProviders.js";
15
+ import { artifactInspectionResultSchema } from "../domain/artifactInspection.js";
15
16
  export { runCapabilityStatus, runProviderStatus, } from "./DirectAnalysisStatus.js";
16
17
  /**
17
18
  * Open one binary, execute one tool, and always release provider resources.
@@ -60,7 +61,7 @@ const authorizeAnalysis = async (authority, tool, arguments_) => {
60
61
  cause: result.error,
61
62
  }));
62
63
  }
63
- if (tool === "inventory_artifact" &&
64
+ if (["inventory_artifact", "inspect_artifact"].includes(tool) &&
64
65
  arguments_.native_mount_approved === true) {
65
66
  const result = await authority.authorize({
66
67
  capability: "native_mount",
@@ -69,7 +70,7 @@ const authorizeAnalysis = async (authority, tool, arguments_) => {
69
70
  environment_names: [],
70
71
  network: "none",
71
72
  mount: true,
72
- operation_identity: "inventory_artifact:native_mount",
73
+ operation_identity: `${tool}:native_mount`,
73
74
  }, "read");
74
75
  if (!result.ok)
75
76
  return err(result.error instanceof PermissionRequiredError
@@ -181,6 +182,7 @@ const executeAnalysisTool = async (input) => {
181
182
  const { session, tool, signal, evidenceProfile } = input;
182
183
  if (tool === "binary_overview" ||
183
184
  tool === "analyze_function" ||
185
+ tool === "inspect_native_api" ||
184
186
  tool === "trace_feature") {
185
187
  const result = await new EnhancedTools(session).execute(tool, input.arguments, signal);
186
188
  if (!result.ok)
@@ -212,6 +214,10 @@ const executeAnalysisTool = async (input) => {
212
214
  rawResult: result.value.rawResult,
213
215
  limitations: result.value.limitations,
214
216
  locations: result.value.locations,
217
+ evidenceLinks: tool === "inspect_artifact"
218
+ ? artifactInspectionResultSchema.parse(result.value.result)
219
+ .evidence_links
220
+ : [],
215
221
  });
216
222
  return { output: evidence, evidence };
217
223
  };
@@ -242,15 +248,18 @@ const prepareSnapshot = async (options) => {
242
248
  return err(new EvidenceIntegrityError("Analysis snapshot target does not match the requested binary"));
243
249
  return ok({ snapshot: loaded.value });
244
250
  };
245
- const replayProviderFor = (session, tool) => tool === "binary_overview" || tool === "trace_feature"
251
+ const replayProviderFor = (session, tool) => isWorkflowEvidenceTool(tool)
246
252
  ? REA_WORKFLOW_PROVIDER
247
253
  : session.providerIdentity(tool);
248
254
  const analysisProfileForEvidence = (session, tool) => {
249
- if (tool !== "binary_overview" && tool !== "trace_feature")
255
+ if (!isWorkflowEvidenceTool(tool))
250
256
  return session.analysisProfile(tool);
251
257
  const upstream = session.analysisProfile();
252
258
  return upstream === undefined ? undefined : workflowAnalysisProfile(upstream);
253
259
  };
260
+ const isWorkflowEvidenceTool = (tool) => tool === "binary_overview" ||
261
+ tool === "inspect_native_api" ||
262
+ tool === "trace_feature";
254
263
  const fileExists = async (path) => {
255
264
  try {
256
265
  await access(path);
@@ -0,0 +1,118 @@
1
+ import { AnalysisOutputError } from "../domain/errors.js";
2
+ import { parseAddressedPage } from "../domain/hopperValues.js";
3
+ import { err, ok } from "../domain/result.js";
4
+ const FEATURE_SEARCH_SOURCES = [
5
+ ["list_strings", "string"],
6
+ ["list_procedures", "procedure"],
7
+ ];
8
+ const STRING_SEARCH_SOURCES = [
9
+ ["list_strings", "string"],
10
+ ];
11
+ /** Trace a bounded feature or string literal through provider xrefs. */
12
+ export const traceLiteralFeature = async (call, input, scope, signal) => {
13
+ const sources = scope === "feature" ? FEATURE_SEARCH_SOURCES : STRING_SEARCH_SOURCES;
14
+ const searched = await literalMatches(call, input, sources, signal);
15
+ if (!searched.ok)
16
+ return searched;
17
+ const residual = new Set();
18
+ const traced = await traceReferences(call, {
19
+ matches: searched.value.matches,
20
+ limit: input.limit,
21
+ operationBudget: input.max_operations - searched.value.operations,
22
+ ...(signal === undefined ? {} : { signal }),
23
+ });
24
+ if (!traced.ok)
25
+ return traced;
26
+ for (const reason of traced.value.residual)
27
+ residual.add(reason);
28
+ const operations = searched.value.operations + traced.value.operations;
29
+ if (operations >= input.max_operations)
30
+ residual.add("Investigation reached the operation budget.");
31
+ if (searched.value.matches.length >= input.limit)
32
+ residual.add("Literal matches reached the configured limit.");
33
+ return ok({
34
+ query: input.query,
35
+ search_mode: "literal",
36
+ operations_used: operations,
37
+ operation_budget: input.max_operations,
38
+ matches: searched.value.matches,
39
+ references: traced.value.references,
40
+ truncated: residual.size > 0,
41
+ residual_unknowns: [...residual],
42
+ });
43
+ };
44
+ const literalMatches = async (call, input, sources, signal) => {
45
+ let operations = 0;
46
+ const matches = [];
47
+ const needle = input.case_sensitive ? input.query : input.query.toLowerCase();
48
+ for (const [tool, type] of sources) {
49
+ let offset = 0;
50
+ while (operations < input.max_operations && matches.length < input.limit) {
51
+ operations += 1;
52
+ const result = await call(tool, { offset, limit: 500 }, signal);
53
+ if (!result.ok)
54
+ return result;
55
+ const page = parseAddressedPage(result.value);
56
+ if (!page.ok)
57
+ return page;
58
+ for (const item of page.value.items) {
59
+ const haystack = input.case_sensitive
60
+ ? item.name
61
+ : item.name.toLowerCase();
62
+ if (haystack.includes(needle))
63
+ matches.push({ type, address: item.address, value: item.name });
64
+ if (matches.length >= input.limit)
65
+ break;
66
+ }
67
+ if (!page.value.hasMore || page.value.nextOffset === null)
68
+ break;
69
+ offset = page.value.nextOffset;
70
+ }
71
+ }
72
+ return ok({ matches, operations });
73
+ };
74
+ const traceReferences = async (call, request) => {
75
+ let operations = 0;
76
+ const references = [];
77
+ const residual = new Set();
78
+ for (const match of request.matches) {
79
+ if (operations >= request.operationBudget) {
80
+ residual.add("Reference traversal stopped at the operation budget.");
81
+ break;
82
+ }
83
+ operations += 1;
84
+ const xrefs = await call("xrefs", { address: match.address }, request.signal);
85
+ if (!xrefs.ok)
86
+ return xrefs;
87
+ if (!Array.isArray(xrefs.value))
88
+ return err(new AnalysisOutputError("xrefs", "provider returned a non-array result"));
89
+ for (const source of xrefs.value) {
90
+ if (typeof source !== "string")
91
+ return err(new AnalysisOutputError("xrefs", "provider returned a non-address value"));
92
+ if (operations >= request.operationBudget) {
93
+ residual.add("Containing-procedure resolution stopped at the operation budget.");
94
+ break;
95
+ }
96
+ operations += 1;
97
+ const resolved = await call("resolve_containing_procedure", { address: source }, request.signal);
98
+ if (!resolved.ok)
99
+ return resolved;
100
+ references.push({
101
+ target_address: match.address,
102
+ source_address: source,
103
+ containing_procedure: resolved.value,
104
+ });
105
+ if (references.length >= request.limit) {
106
+ residual.add("Reference results reached the configured limit.");
107
+ break;
108
+ }
109
+ }
110
+ if (references.length >= request.limit)
111
+ break;
112
+ }
113
+ return ok({
114
+ references,
115
+ operations,
116
+ residual: [...residual],
117
+ });
118
+ };