rea-agents 2.4.0 → 2.5.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 (186) hide show
  1. package/README.md +80 -18
  2. package/bridge/ghidra/ReaGhidraBridge.java +48 -1
  3. package/bridge/hopper_bridge.py +259 -24
  4. package/dist/application/AnalysisClientCleanup.js +14 -0
  5. package/dist/application/ApplicationWorkflowEvidenceResolver.js +31 -13
  6. package/dist/application/BinarySession.js +15 -4
  7. package/dist/application/BinarySessionStatus.js +31 -1
  8. package/dist/application/BrowserEvidence.js +14 -0
  9. package/dist/application/BrowserScenarioCapturePort.js +1 -0
  10. package/dist/application/BrowserScenarioCaptureService.js +53 -0
  11. package/dist/application/BrowserScenarioEvidence.js +40 -0
  12. package/dist/application/CallPathTracing.js +160 -0
  13. package/dist/application/CapabilityInventory.js +68 -12
  14. package/dist/application/CompositeProvider.js +10 -1
  15. package/dist/application/DirectAnalysis.js +7 -2
  16. package/dist/application/EnhancedLiteralTracing.js +118 -0
  17. package/dist/application/EnhancedTools.js +35 -121
  18. package/dist/application/JavaScriptApplicationWorkflowEvidence.js +13 -0
  19. package/dist/application/JavaScriptApplicationWorkflowService.js +27 -1
  20. package/dist/application/JavaScriptRuntimeObservationEvidence.js +34 -0
  21. package/dist/application/JavaScriptRuntimeObservationPort.js +1 -0
  22. package/dist/application/JavaScriptRuntimeObservationService.js +46 -0
  23. package/dist/application/JavaScriptSemanticGraphAsyncProjection.js +147 -0
  24. package/dist/application/JavaScriptSemanticGraphBuilder.js +34 -6
  25. package/dist/application/JavaScriptSemanticGraphChildProcessProjection.js +156 -0
  26. package/dist/application/JavaScriptSemanticGraphConstruction.js +2 -2
  27. package/dist/application/JavaScriptSemanticGraphDataProjection.js +203 -0
  28. package/dist/application/JavaScriptSemanticGraphFingerprintProjection.js +25 -0
  29. package/dist/application/JavaScriptSemanticGraphFlowProjection.js +129 -8
  30. package/dist/application/JavaScriptSemanticGraphObjectProjection.js +81 -0
  31. package/dist/application/JavaScriptSemanticGraphProjection.js +14 -7
  32. package/dist/application/JavaScriptSemanticGraphResourceProjection.js +59 -0
  33. package/dist/application/JavaScriptSemanticGraphValueProjection.js +77 -0
  34. package/dist/application/ProcessReactiveEffects.js +6 -0
  35. package/dist/application/ProcessReactiveHarness.js +16 -1
  36. package/dist/application/ProjectPermissionStore.js +1 -0
  37. package/dist/application/ReconstructionObligationAuthority.js +72 -0
  38. package/dist/application/ReconstructionObligationCandidates.js +265 -0
  39. package/dist/application/ReconstructionObligationLedgerDiagnostics.js +47 -0
  40. package/dist/application/ReconstructionObligationLedgerEvaluation.js +263 -0
  41. package/dist/application/ReconstructionObligationLedgerProjection.js +86 -0
  42. package/dist/application/ReconstructionObligationLedgerService.js +72 -0
  43. package/dist/application/ReconstructionObligationOriginalCases.js +33 -0
  44. package/dist/application/ReconstructionObligationPolicies.js +132 -0
  45. package/dist/application/ReconstructionReadinessService.js +48 -0
  46. package/dist/artifacts/ArtifactProvider.js +87 -22
  47. package/dist/browser/BrowserScenarioSecrets.js +53 -0
  48. package/dist/browser/BrowserScenarioSessionPort.js +1 -0
  49. package/dist/browser/CdpBrowserProvider.js +2 -2
  50. package/dist/browser/CdpCaptureDocuments.js +57 -0
  51. package/dist/browser/CdpCaptureStorage.js +71 -29
  52. package/dist/browser/CdpCaptureStorageFingerprints.js +328 -0
  53. package/dist/browser/CdpEndpoint.js +8 -6
  54. package/dist/browser/CdpPageCapture.js +2 -0
  55. package/dist/browser/JavaScriptRuntimeScope.js +38 -0
  56. package/dist/browser/PlaywrightBrowserScenarioProvider.js +253 -0
  57. package/dist/browser/PlaywrightExecutionBoundary.js +42 -0
  58. package/dist/browser/PlaywrightScenarioActions.js +62 -0
  59. package/dist/browser/PlaywrightScenarioArtifacts.js +238 -0
  60. package/dist/browser/PlaywrightScenarioBrowser.js +144 -0
  61. package/dist/browser/PlaywrightScenarioEvents.js +301 -0
  62. package/dist/browser/PlaywrightScenarioSession.js +300 -0
  63. package/dist/browser/V8InspectorCaptureProjection.js +110 -0
  64. package/dist/browser/V8InspectorEndpoint.js +72 -0
  65. package/dist/browser/V8InspectorProvider.js +267 -0
  66. package/dist/catalogIdentity.js +8 -0
  67. package/dist/cli/artifactCommands.js +39 -0
  68. package/dist/cli/coreAnalysisCommands.js +37 -0
  69. package/dist/cli.js +4 -0
  70. package/dist/cliApplicationCommands.js +30 -2
  71. package/dist/cliBrowserAdvancedCommands.js +17 -5
  72. package/dist/cliBrowserCommands.js +1 -0
  73. package/dist/cliBrowserScenarioCommands.js +43 -0
  74. package/dist/cliCommandNames.js +8 -0
  75. package/dist/cliJavaScriptRuntimeCommands.js +164 -0
  76. package/dist/cliObservationOptions.js +4 -0
  77. package/dist/cliPolicyCommands.js +1 -0
  78. package/dist/config/environment.js +12 -0
  79. package/dist/config/parseConfig.js +88 -17
  80. package/dist/contracts/analysisSearchContract.js +14 -0
  81. package/dist/contracts/applicationToolContracts.js +86 -2
  82. package/dist/contracts/applicationWorkflowInputContracts.js +18 -0
  83. package/dist/contracts/artifactToolContracts.js +22 -0
  84. package/dist/contracts/browserProviderToolContracts.js +7 -0
  85. package/dist/contracts/browserScenarioToolContracts.js +81 -0
  86. package/dist/contracts/browserToolContracts.js +88 -98
  87. package/dist/contracts/enhancedInputs.js +25 -6
  88. package/dist/contracts/functionInstructionContract.js +8 -0
  89. package/dist/contracts/hopperMemoryContracts.js +22 -0
  90. package/dist/contracts/javascriptApplicationWorkflowExamples.js +42 -0
  91. package/dist/contracts/javascriptRuntimeObservationToolContracts.js +58 -0
  92. package/dist/contracts/reconstructionReadinessExample.js +436 -0
  93. package/dist/contracts/toolContractExamples.js +5 -0
  94. package/dist/contracts/toolContractTypes.js +1 -0
  95. package/dist/contracts/toolContracts.js +13 -21
  96. package/dist/contracts/toolEffects.js +21 -0
  97. package/dist/contracts/toolOutputSchemaGroups.js +70 -20
  98. package/dist/contracts/toolOutputSchemaPrimitives.js +37 -0
  99. package/dist/domain/analysisErrorProjection.js +8 -0
  100. package/dist/domain/artifactInspection.js +347 -0
  101. package/dist/domain/browserCaptureComparison.js +82 -0
  102. package/dist/domain/browserObservation.js +8 -1
  103. package/dist/domain/browserObservationErrors.js +1 -0
  104. package/dist/domain/browserObservationSchemas.js +26 -0
  105. package/dist/domain/browserScenario.js +165 -0
  106. package/dist/domain/browserScenarioCapture.js +57 -0
  107. package/dist/domain/browserScenarioCaptureValues.js +204 -0
  108. package/dist/domain/browserScenarioDiff.js +324 -0
  109. package/dist/domain/browserScenarioDiffValues.js +157 -0
  110. package/dist/domain/browserScenarioNormalization.js +45 -0
  111. package/dist/domain/browserScenarioValues.js +372 -0
  112. package/dist/domain/hopperValues.js +10 -0
  113. package/dist/domain/javascriptRuntimeObservation.js +170 -0
  114. package/dist/domain/javascriptRuntimeReconciliationParsing.js +120 -2
  115. package/dist/domain/javascriptRuntimeReconciliationResult.js +3 -3
  116. package/dist/domain/javascriptRuntimeReconciliationRuntime.js +2 -2
  117. package/dist/domain/javascriptRuntimeReconciliationSchemas.js +1 -1
  118. package/dist/domain/javascriptSemanticAnalysis.js +5 -4
  119. package/dist/domain/javascriptSemanticAsyncEffects.js +253 -0
  120. package/dist/domain/javascriptSemanticChildProcesses.js +237 -0
  121. package/dist/domain/javascriptSemanticDataEffectHelpers.js +125 -0
  122. package/dist/domain/javascriptSemanticDataEffects.js +284 -0
  123. package/dist/domain/javascriptSemanticDerivedAnalysis.js +41 -0
  124. package/dist/domain/javascriptSemanticFingerprints.js +174 -0
  125. package/dist/domain/javascriptSemanticIr.js +21 -1
  126. package/dist/domain/javascriptSemanticObjects.js +115 -0
  127. package/dist/domain/javascriptSemanticPromises.js +290 -0
  128. package/dist/domain/javascriptSemanticResources.js +136 -0
  129. package/dist/domain/javascriptStaticAnalysis.js +4 -3
  130. package/dist/domain/javascriptStaticAnalysisBundler.js +38 -0
  131. package/dist/domain/javascriptStaticAnalysisCalls.js +23 -1
  132. package/dist/domain/javascriptStaticAnalysisHelpers.js +8 -0
  133. package/dist/domain/processCapture.js +8 -2
  134. package/dist/domain/processReactiveScenario.js +1 -0
  135. package/dist/domain/processReactiveTransition.js +9 -4
  136. package/dist/domain/providerCleanupError.js +11 -0
  137. package/dist/domain/reconstructionObligationLedgerSchemas.js +278 -0
  138. package/dist/domain/reconstructionReadiness.js +121 -0
  139. package/dist/domain/reconstructionReadinessFindingHelpers.js +17 -0
  140. package/dist/domain/reconstructionReadinessIntegrityFindings.js +118 -0
  141. package/dist/domain/reconstructionReadinessJourneyFindings.js +193 -0
  142. package/dist/domain/reconstructionReadinessSchemas.js +240 -0
  143. package/dist/domain/sourceToBundleComparison.js +335 -0
  144. package/dist/domain/sourceToBundleComparisonSchemas.js +133 -0
  145. package/dist/domain/sourceToBundleSignals.js +202 -0
  146. package/dist/domain/webCaptureDiff.js +141 -27
  147. package/dist/domain/webScreenshot.js +1 -1
  148. package/dist/generatedPackageMetadata.js +1 -1
  149. package/dist/ghidra/GhidraDefaults.js +1 -1
  150. package/dist/ghidra/GhidraFunctionValues.js +13 -1
  151. package/dist/ghidra/GhidraProviderCapabilities.js +7 -0
  152. package/dist/hopper/HopperCleanup.js +158 -0
  153. package/dist/hopper/HopperClient.js +69 -99
  154. package/dist/hopper/HopperProvider.js +20 -1
  155. package/dist/hopper/HopperRequestQueue.js +187 -0
  156. package/dist/hopper/HopperResponseStream.js +46 -0
  157. package/dist/hopper/protocol.js +1 -0
  158. package/dist/main/transport.js +24 -24
  159. package/dist/main.js +6 -0
  160. package/dist/mcpDoctor.js +25 -2
  161. package/dist/process/ProcessCommandIdentity.js +19 -0
  162. package/dist/process/ProcessOwnership.js +11 -8
  163. package/dist/server/DynamicToolAvailability.js +70 -0
  164. package/dist/server/createServer.js +46 -1
  165. package/dist/server/lifecycleProgress.js +10 -0
  166. package/dist/server/permissionFailure.js +12 -0
  167. package/dist/server/registerApplicationTools/compareSourceToBundle.js +35 -0
  168. package/dist/server/registerApplicationTools/helpers.js +11 -7
  169. package/dist/server/registerApplicationTools/obligationLedger.js +27 -0
  170. package/dist/server/registerApplicationTools/readiness.js +55 -0
  171. package/dist/server/registerApplicationTools.js +6 -0
  172. package/dist/server/registerArtifactTools.js +9 -1
  173. package/dist/server/registerBrowserScenarioTool.js +25 -0
  174. package/dist/server/registerBrowserTools.js +2 -2
  175. package/dist/server/registerCloseLifecycleTool.js +57 -0
  176. package/dist/server/registerEnhancedTools.js +16 -5
  177. package/dist/server/registerEvidenceResources.js +4 -0
  178. package/dist/server/registerEvidenceTools.js +9 -2
  179. package/dist/server/registerJavaScriptRuntimeObservationTools.js +37 -0
  180. package/dist/server/registerReconstructionObligationLedgerResource.js +39 -0
  181. package/dist/server/registerReconstructionReadinessResource.js +39 -0
  182. package/dist/server/registerSessionStatusTool.js +11 -8
  183. package/dist/server/registerSessionTools.js +11 -57
  184. package/dist/server/sessionAvailabilityPolicy.js +10 -8
  185. package/package.json +12 -6
  186. package/skills/reverse-engineer-anything/SKILL.md +2 -2
@@ -0,0 +1,53 @@
1
+ import { createBrowserScenarioEvidence } from "./BrowserScenarioEvidence.js";
2
+ import { digestJson } from "./JavaScriptReplayPlanning.js";
3
+ import { AnalysisCapabilityUnavailableError, AnalysisProtocolError, PermissionRequiredError, } from "../domain/errors.js";
4
+ import { isLiteralLoopbackHostname } from "../domain/browserObservation.js";
5
+ import { err } from "../domain/result.js";
6
+ const OPERATION = "capture_browser_scenario";
7
+ const PROVIDER_ID = "rea-playwright-browser-scenario";
8
+ const networkScope = (origins) => origins.every((origin) => isLiteralLoopbackHostname(new URL(origin).hostname))
9
+ ? "loopback"
10
+ : "external";
11
+ const authorizeScenario = async (authority, scenario) => {
12
+ if (authority === undefined)
13
+ return err(new AnalysisCapabilityUnavailableError(PROVIDER_ID, OPERATION, "browser scenario permission policy is not configured"));
14
+ const authorized = await authority.authorize({
15
+ capability: "browser_automate",
16
+ roots: [],
17
+ executables: scenario.browser.mode === "launch"
18
+ ? [scenario.browser.executable_path]
19
+ : [],
20
+ environment_names: scenario.secrets.map(({ environment_variable: name }) => name),
21
+ origins: [
22
+ ...(scenario.browser.mode === "connect"
23
+ ? [scenario.browser.cdp_endpoint]
24
+ : []),
25
+ ...scenario.allowed_origins,
26
+ ],
27
+ network: networkScope(scenario.allowed_origins),
28
+ mount: false,
29
+ operation_identity: `${OPERATION}:${digestJson(scenario)}`,
30
+ }, "write");
31
+ if (!authorized.ok)
32
+ return err(authorized.error instanceof PermissionRequiredError
33
+ ? authorized.error
34
+ : new AnalysisProtocolError(authorized.error.message, {
35
+ cause: authorized.error,
36
+ }));
37
+ return { ok: true, value: true };
38
+ };
39
+ /** Authorize and execute one controlled browser scenario. */
40
+ export const captureBrowserScenario = async (provider, authority, scenario, options = {}) => {
41
+ const authorized = await authorizeScenario(authority, scenario);
42
+ if (!authorized.ok)
43
+ return authorized;
44
+ if (provider === undefined)
45
+ return err(new AnalysisCapabilityUnavailableError(PROVIDER_ID, OPERATION, "browser scenario provider is not configured"));
46
+ const captured = await provider.captureScenario(scenario, options);
47
+ return captured.ok
48
+ ? {
49
+ ok: true,
50
+ value: createBrowserScenarioEvidence(scenario, captured.value, provider.identity()),
51
+ }
52
+ : captured;
53
+ };
@@ -0,0 +1,40 @@
1
+ import { sanitizeBrowserUrl } from "../domain/browserObservation.js";
2
+ import { createEvidence, } from "../domain/evidence.js";
3
+ import { jsonValueSchema } from "../domain/jsonValue.js";
4
+ import { digestJson } from "./JavaScriptReplayPlanning.js";
5
+ const browserScenarioParameters = (scenario) => ({
6
+ scenario_sha256: digestJson(scenario),
7
+ browser_mode: scenario.browser.mode,
8
+ start_url: sanitizeBrowserUrl(scenario.start_url.url),
9
+ allowed_origins: scenario.allowed_origins,
10
+ actions: scenario.actions.map(({ step_id, action }) => ({
11
+ step_id,
12
+ action,
13
+ })),
14
+ secret_declarations: scenario.secrets.map(({ secret_id, environment_variable, purpose, redaction }) => ({
15
+ secret_id,
16
+ environment_variable,
17
+ purpose,
18
+ redaction,
19
+ })),
20
+ capture: scenario.capture,
21
+ limits: scenario.limits,
22
+ });
23
+ /** Create Evidence v2 without retaining resolved scenario secret values. */
24
+ export const createBrowserScenarioEvidence = (scenario, capture, provider) => createEvidence(undefined, provider, {
25
+ predicateType: "rea.browser-scenario-capture/v1",
26
+ operation: "capture_browser_scenario",
27
+ parameters: browserScenarioParameters(scenario),
28
+ result: jsonValueSchema.parse(capture),
29
+ confidence: "observed",
30
+ authority: "controlled-replay",
31
+ environment: {
32
+ id: `${capture.browser.product}@${capture.browser.version}`,
33
+ platform: process.platform,
34
+ architecture: process.arch,
35
+ isolation: capture.browser.process_ownership === "provider-owned"
36
+ ? "process"
37
+ : "none",
38
+ },
39
+ limitations: capture.limitations,
40
+ });
@@ -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);
@@ -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"],
@@ -33,8 +45,8 @@ const ENHANCED_REQUIREMENTS = {
33
45
  "resolve_containing_procedure",
34
46
  ],
35
47
  };
36
- /** Build stable per-operation availability without hiding familiar tools. */
37
- export const buildCapabilityInventory = (sessionStatus, policy) => {
48
+ /** Build stable per-operation availability for discovery and tool visibility. */
49
+ export const buildCapabilityInventory = (sessionStatus, policy, clientFeatures = NO_CLIENT_FEATURES) => {
38
50
  const status = statusSchema.parse(sessionStatus);
39
51
  const descriptors = new Map(status.capabilities.map((descriptor) => [
40
52
  descriptor.operation,
@@ -53,12 +65,19 @@ export const buildCapabilityInventory = (sessionStatus, policy) => {
53
65
  descriptors,
54
66
  policy,
55
67
  });
68
+ const clientRequirements = clientRequirementsFor(contract.name, clientFeatures);
69
+ const clientBlocked = clientRequirements.missing_required.length > 0;
56
70
  return {
57
71
  name: contract.name,
58
72
  surface: contract.kind,
59
- available: availability.reason === "available",
60
- reason: availability.reason,
61
- remediation: availability.remediation,
73
+ available: availability.reason === "available" && !clientBlocked,
74
+ reason: clientBlocked
75
+ ? "client_capability_missing"
76
+ : availability.reason,
77
+ remediation: clientBlocked
78
+ ? `Use an MCP client that supports: ${clientRequirements.missing_required.join(", ")}.`
79
+ : availability.remediation,
80
+ client_requirements: clientRequirements,
62
81
  effects: { ...contract.effects },
63
82
  annotations: {
64
83
  read_only: contract.annotations.readOnlyHint ?? false,
@@ -69,6 +88,18 @@ export const buildCapabilityInventory = (sessionStatus, policy) => {
69
88
  };
70
89
  }).sort((left, right) => left.name.localeCompare(right.name));
71
90
  };
91
+ const clientRequirementsFor = (name, clientFeatures) => {
92
+ const requirements = CLIENT_FEATURE_REQUIREMENTS[name] ?? {
93
+ required: [],
94
+ optional: [],
95
+ };
96
+ return {
97
+ required: [...requirements.required],
98
+ optional: [...requirements.optional],
99
+ missing_required: requirements.required.filter((feature) => !clientFeatures[feature]),
100
+ missing_optional: requirements.optional.filter((feature) => !clientFeatures[feature]),
101
+ };
102
+ };
72
103
  const availabilityFor = (context) => {
73
104
  const javascriptApplication = javascriptApplicationAvailability(context);
74
105
  if (javascriptApplication !== null)
@@ -94,6 +125,9 @@ const javascriptApplicationAvailability = ({ name, policy, }) => {
94
125
  : null;
95
126
  };
96
127
  const policyAvailability = ({ name, kind, policy, }) => {
128
+ const browser = browserPolicyAvailability(name, kind, policy);
129
+ if (browser !== null)
130
+ return browser;
97
131
  if (name === "capture_process_scenario" && !policy.processCaptureEnabled)
98
132
  return {
99
133
  reason: "policy_disabled",
@@ -121,26 +155,43 @@ const policyAvailability = ({ name, kind, policy, }) => {
121
155
  reason: "policy_disabled",
122
156
  remediation: "Configure or grant an evidence_read root.",
123
157
  };
124
- if (kind === "browser-provider")
125
- return policy.browserObservationEnabled === true
158
+ if (kind === "electron-provider")
159
+ return policy.electronObservationEnabled === true
126
160
  ? { reason: "available", remediation: null }
127
161
  : {
128
162
  reason: "policy_disabled",
129
- remediation: "Enable browser observation and configure exact CDP endpoint and page origins.",
163
+ remediation: "Enable Electron observation and configure a loopback CDP endpoint and canonical file roots.",
130
164
  };
131
- if (kind === "electron-provider")
132
- return policy.electronObservationEnabled === true
165
+ if (kind === "runtime-provider")
166
+ return policy.v8InspectorObservationEnabled === true
133
167
  ? { reason: "available", remediation: null }
134
168
  : {
135
169
  reason: "policy_disabled",
136
- remediation: "Enable Electron observation and configure a loopback CDP endpoint and canonical file roots.",
170
+ remediation: "Enable V8 Inspector observation and configure exact loopback endpoints plus canonical file roots or exact origins.",
137
171
  };
138
172
  if (kind === "session")
139
173
  return { reason: "available", remediation: null };
140
174
  return null;
141
175
  };
176
+ const browserPolicyAvailability = (name, kind, policy) => {
177
+ if (name === "capture_browser_scenario")
178
+ return policy.browserScenarioEnabled === true
179
+ ? { reason: "available", remediation: null }
180
+ : {
181
+ reason: "policy_disabled",
182
+ remediation: "Enable browser scenarios and configure exact executable roots or loopback CDP endpoints, page origins, and environment secret names.",
183
+ };
184
+ if (kind !== "browser-provider")
185
+ return null;
186
+ return policy.browserObservationEnabled === true
187
+ ? { reason: "available", remediation: null }
188
+ : {
189
+ reason: "policy_disabled",
190
+ remediation: "Enable browser observation and configure exact CDP endpoint and page origins.",
191
+ };
192
+ };
142
193
  const targetAvailability = ({ kind, targetKind, targetOpen, }) => {
143
- if (!targetOpen)
194
+ if (!targetOpen && (kind === "official-proxy" || kind === "enhanced"))
144
195
  return {
145
196
  reason: "target_required",
146
197
  remediation: "Call open_binary with a supported local target.",
@@ -159,6 +210,11 @@ const providerAvailability = ({ descriptors, kind, name, }) => {
159
210
  if (kind === "enhanced")
160
211
  return composedAvailability(name, descriptors);
161
212
  const descriptor = descriptors.get(name);
213
+ if (descriptor === undefined &&
214
+ (kind === "artifact-provider" ||
215
+ kind === "managed-provider" ||
216
+ kind === "native-provider"))
217
+ return { reason: "available", remediation: null };
162
218
  if (descriptor === undefined)
163
219
  return {
164
220
  reason: "provider_missing",
@@ -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
@@ -212,6 +213,10 @@ const executeAnalysisTool = async (input) => {
212
213
  rawResult: result.value.rawResult,
213
214
  limitations: result.value.limitations,
214
215
  locations: result.value.locations,
216
+ evidenceLinks: tool === "inspect_artifact"
217
+ ? artifactInspectionResultSchema.parse(result.value.result)
218
+ .evidence_links
219
+ : [],
215
220
  });
216
221
  return { output: evidence, evidence };
217
222
  };
@@ -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
+ };