rea-agents 2.5.0 → 3.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 (253) hide show
  1. package/README.md +20 -12
  2. package/bridge/ghidra/ReaGhidraBridge.java +477 -4
  3. package/bridge/hopper_bridge.py +54 -4
  4. package/dist/application/AnalysisContextQueries.js +142 -0
  5. package/dist/application/AnalysisSnapshotCache.js +4 -2
  6. package/dist/application/ApplicationWorkflowEvidenceResolver.js +19 -11
  7. package/dist/application/ArtifactInventory/policy.js +17 -0
  8. package/dist/application/ArtifactInventory/reader.js +1 -4
  9. package/dist/application/ArtifactInventory/scanCanonical.js +0 -3
  10. package/dist/application/ArtifactInventory/scanReader.js +7 -7
  11. package/dist/application/ArtifactInventory/types.js +1 -5
  12. package/dist/application/BinarySession.js +19 -20
  13. package/dist/application/BinarySessionComposition.js +21 -0
  14. package/dist/application/BinarySessionRecords.js +106 -10
  15. package/dist/application/BinarySessionStatus.js +11 -5
  16. package/dist/application/BrowserEvidence.js +3 -5
  17. package/dist/application/CapabilityClientRequirements.js +26 -0
  18. package/dist/application/CapabilityInventory.js +140 -55
  19. package/dist/application/ClientRegistrationStatus.js +4 -1
  20. package/dist/application/CrossVersionInventory.js +7 -6
  21. package/dist/application/DirectAnalysis.js +6 -2
  22. package/dist/application/ElectronActiveEvidence.js +55 -0
  23. package/dist/application/ElectronActiveObservationPort.js +1 -0
  24. package/dist/application/ElectronActiveObservationService.js +81 -0
  25. package/dist/application/EnhancedTools.js +49 -17
  26. package/dist/application/EvidenceBundleCommands.js +5 -1
  27. package/dist/application/EvidenceLedger.js +53 -17
  28. package/dist/application/JavaScriptArtifactAnalysis.js +13 -4
  29. package/dist/application/JavaScriptReplayPermission.js +1 -1
  30. package/dist/application/JavaScriptReplayService.js +23 -15
  31. package/dist/application/LazyAnalysisProvider.js +77 -0
  32. package/dist/application/LoopbackReplay.js +1 -1
  33. package/dist/application/ManagedRuntimeCorrelationService.js +9 -5
  34. package/dist/application/NativeApiInspection.js +80 -0
  35. package/dist/application/NodeRuntimeCharacterizationService.js +8 -9
  36. package/dist/application/ProcessCaptureLifecycle.js +5 -0
  37. package/dist/application/ProcessCli.js +2 -1
  38. package/dist/application/ProcessHarness.js +8 -3
  39. package/dist/application/ProcessNormalization.js +2 -1
  40. package/dist/application/ProjectPermissionStore.js +1 -0
  41. package/dist/application/ReferenceSourceImport.js +2 -2
  42. package/dist/application/SessionProviderRouter.js +6 -6
  43. package/dist/application/SetupClientConfiguration.js +10 -3
  44. package/dist/application/WindowsCapabilities.js +5 -4
  45. package/dist/application/runtime.js +26 -9
  46. package/dist/artifacts/ArtifactProvider.js +8 -10
  47. package/dist/artifacts/SafeOutputTree.js +7 -8
  48. package/dist/browser/CdpCaptureStorageFingerprints.js +10 -7
  49. package/dist/browser/CdpElectronInspection.js +3 -1
  50. package/dist/browser/JavaScriptRuntimeScope.js +5 -0
  51. package/dist/browser/PlaywrightBrowserScenarioProvider.js +8 -2
  52. package/dist/browser/PlaywrightElectronActiveActions.js +324 -0
  53. package/dist/browser/PlaywrightElectronActiveProvider.js +294 -0
  54. package/dist/browser/PlaywrightScenarioBrowser.js +4 -4
  55. package/dist/browser/PlaywrightScenarioSession.js +4 -4
  56. package/dist/browser/SensitiveTextCapture.js +1 -1
  57. package/dist/browser/V8InspectorCaptureProjection.js +1 -0
  58. package/dist/browser/V8InspectorProvider.js +3 -3
  59. package/dist/catalogIdentity.js +15 -9
  60. package/dist/cli/artifactCommands.js +5 -3
  61. package/dist/cli/coreAnalysisCommands.js +41 -0
  62. package/dist/cli/managedCommands.js +1 -1
  63. package/dist/cliApplicationCommands.js +2 -2
  64. package/dist/cliBrowserAdvancedCommands.js +11 -5
  65. package/dist/cliBrowserCommands.js +14 -7
  66. package/dist/cliCommandNames.js +2 -0
  67. package/dist/cliElectronCommands.js +46 -4
  68. package/dist/cliJavaScriptRuntimeCommands.js +14 -6
  69. package/dist/cliObservationOptions.js +4 -0
  70. package/dist/cliPolicyCommands.js +1 -0
  71. package/dist/config/browserScenario.js +77 -0
  72. package/dist/config/electronAutomation.js +35 -0
  73. package/dist/config/environment.js +6 -0
  74. package/dist/config/javascriptReplay.js +41 -0
  75. package/dist/config/managedRuntime.js +25 -0
  76. package/dist/config/parseConfig.js +56 -200
  77. package/dist/config/parsers.js +10 -1
  78. package/dist/config/passiveObservation.js +157 -0
  79. package/dist/config/processCapture.js +47 -0
  80. package/dist/contracts/applicationToolContracts.js +2 -5
  81. package/dist/contracts/applicationWorkflowInputContracts.js +86 -96
  82. package/dist/contracts/artifactToolContracts.js +35 -37
  83. package/dist/contracts/browserToolContracts.js +2 -2
  84. package/dist/contracts/electronToolContracts.js +30 -1
  85. package/dist/contracts/enhancedInputs.js +21 -27
  86. package/dist/contracts/functionWorkflowToolContracts.js +29 -0
  87. package/dist/contracts/managedToolContracts.js +4 -9
  88. package/dist/contracts/managedWorkflowExamples.js +12 -9
  89. package/dist/contracts/managedWorkflowToolContracts.js +30 -27
  90. package/dist/contracts/nativeToolContracts.js +2 -3
  91. package/dist/contracts/promptContracts.js +14 -15
  92. package/dist/contracts/sessionToolSchemas.js +61 -0
  93. package/dist/contracts/toolContractExamples.js +4 -0
  94. package/dist/contracts/toolContractHelpers.js +23 -0
  95. package/dist/contracts/toolContracts.js +12 -67
  96. package/dist/contracts/toolEffects.js +19 -0
  97. package/dist/contracts/toolOutputSchemaGroups.js +66 -11
  98. package/dist/contracts/toolOutputSchemaPrimitives.js +76 -26
  99. package/dist/contracts/toolSchemaMetadata.js +128 -0
  100. package/dist/doctorRuntime.js +1 -1
  101. package/dist/domain/analysisErrorPresentation.js +8 -2
  102. package/dist/domain/analysisErrorProjection.js +1 -0
  103. package/dist/domain/androidApplication.js +2 -0
  104. package/dist/domain/appleApplication.js +2 -0
  105. package/dist/domain/binaryTarget.js +7 -5
  106. package/dist/domain/binaryTargetTypes.js +1 -0
  107. package/dist/domain/browserCaptureComparison.js +21 -69
  108. package/dist/domain/browserObservation.js +22 -9
  109. package/dist/domain/bytecodeProvider.js +177 -0
  110. package/dist/domain/callPath.js +3 -2
  111. package/dist/domain/conformancePackage.js +169 -0
  112. package/dist/domain/conformanceReplay.js +104 -0
  113. package/dist/domain/conformanceTrustGate.js +328 -0
  114. package/dist/domain/customProtocolCapture.js +137 -0
  115. package/dist/domain/electronActiveObservation.js +277 -0
  116. package/dist/domain/electronObservation.js +15 -11
  117. package/dist/domain/electronStaticAnalysisValues.js +4 -1
  118. package/dist/domain/eventProcessTree.js +217 -0
  119. package/dist/domain/evidenceBundle.js +47 -0
  120. package/dist/domain/functionComparisonNormalization.js +15 -8
  121. package/dist/domain/functionDossierEvidence.js +9 -4
  122. package/dist/domain/hopperStartupFailure.js +1 -1
  123. package/dist/domain/hopperValues.js +68 -68
  124. package/dist/domain/inputIssueProjection.js +11 -2
  125. package/dist/domain/investigationWorkspace.js +16 -15
  126. package/dist/domain/javascriptExportShapeComparisonSchemas.js +2 -2
  127. package/dist/domain/javascriptReplay.js +79 -63
  128. package/dist/domain/javascriptRuntimeObservation.js +1 -0
  129. package/dist/domain/javascriptRuntimeReconciliationActive.js +89 -0
  130. package/dist/domain/javascriptRuntimeReconciliationGraph.js +11 -10
  131. package/dist/domain/javascriptRuntimeReconciliationMatching.js +57 -49
  132. package/dist/domain/javascriptRuntimeReconciliationParsing.js +5 -1
  133. package/dist/domain/javascriptRuntimeReconciliationResult.js +21 -14
  134. package/dist/domain/javascriptRuntimeReconciliationSchemas.js +1 -1
  135. package/dist/domain/javascriptSemanticAnalysis.js +8 -12
  136. package/dist/domain/javascriptSourceParser.js +15 -0
  137. package/dist/domain/javascriptStaticAnalysis.js +8 -12
  138. package/dist/domain/knownPageCoverage.js +15 -7
  139. package/dist/domain/mobileApplicationInvestigation.js +159 -0
  140. package/dist/domain/nativeApiBoundary.js +138 -0
  141. package/dist/domain/nodeRuntimeCharacterization.js +14 -13
  142. package/dist/domain/objcSwiftMetadata.js +150 -0
  143. package/dist/domain/packageAnalysis.js +123 -0
  144. package/dist/domain/peInspection.js +196 -0
  145. package/dist/domain/processCaptureReactiveValidation.js +8 -3
  146. package/dist/domain/processCaptureValidation.js +7 -3
  147. package/dist/domain/processComparison.js +12 -7
  148. package/dist/domain/processObservation.js +5 -6
  149. package/dist/domain/processReactiveMatching.js +14 -7
  150. package/dist/domain/processReactiveTransition.js +9 -3
  151. package/dist/domain/processScenario.js +6 -3
  152. package/dist/domain/processTraceComparison.js +3 -2
  153. package/dist/domain/protocolCapture.js +245 -0
  154. package/dist/domain/reconstructionReadinessSchemas.js +30 -2
  155. package/dist/domain/referenceSourceImportParsing.js +2 -2
  156. package/dist/domain/replayMachineRuntime.js +5 -3
  157. package/dist/domain/sourceToBundleComparison.js +2 -2
  158. package/dist/domain/sourceToBundleComparisonSchemas.js +8 -8
  159. package/dist/domain/sourceToBundleSignals.js +7 -1
  160. package/dist/domain/staticRuntimeCorrelation.js +7 -5
  161. package/dist/domain/webBundleAnalysis.js +14 -14
  162. package/dist/domain/webCaptureDiff.js +15 -12
  163. package/dist/dotnet/ManagedNativeBoundaryInspector.js +2 -3
  164. package/dist/dotnet/ManagedStaticProvider.js +34 -5
  165. package/dist/generatedMcpToolCatalog.js +37 -0
  166. package/dist/generatedPackageMetadata.js +4 -4
  167. package/dist/ghidra/GhidraClient.js +1 -1
  168. package/dist/ghidra/GhidraClientStartup.js +1 -1
  169. package/dist/ghidra/GhidraDefaults.js +1 -1
  170. package/dist/ghidra/GhidraFunctionValues.js +44 -16
  171. package/dist/ghidra/GhidraInventoryValues.js +57 -47
  172. package/dist/ghidra/GhidraProvider.js +18 -10
  173. package/dist/ghidra/GhidraProviderCapabilities.js +45 -39
  174. package/dist/ghidra/GhidraSocketConnection.js +1 -1
  175. package/dist/ghidra/protocol.js +19 -42
  176. package/dist/hopper/BridgeLauncher.js +3 -1
  177. package/dist/hopper/HopperAnalysisProfile.js +2 -4
  178. package/dist/hopper/HopperCleanup.js +5 -6
  179. package/dist/hopper/HopperClient.js +27 -3
  180. package/dist/hopper/HopperProvider.js +42 -44
  181. package/dist/hopper/HopperRequestQueue.js +32 -4
  182. package/dist/hopper/HopperResponseStream.js +2 -2
  183. package/dist/hopper/LinuxPrivateDisplayProbe.js +16 -10
  184. package/dist/hopper/protocol.js +63 -21
  185. package/dist/main/reload.js +1 -6
  186. package/dist/main/state.js +0 -14
  187. package/dist/main/transport.js +31 -41
  188. package/dist/main.js +17 -13
  189. package/dist/mcpDoctor.js +4 -26
  190. package/dist/mcpStartupPolicy.js +14 -0
  191. package/dist/process/PrivateRuntimeRoot.js +17 -0
  192. package/dist/process/ProcessOwnership.js +64 -128
  193. package/dist/process/ProcessOwnershipIdentity.js +13 -0
  194. package/dist/process/ProcessOwnershipObservation.js +88 -0
  195. package/dist/process/ProcessOwnershipProcessTree.js +22 -0
  196. package/dist/process/ProviderDeadline.js +8 -12
  197. package/dist/process/WindowsAuthority.js +32 -0
  198. package/dist/reference/ReferenceSourceReader.js +3 -3
  199. package/dist/reference/ReferenceSourceReaderEntries.js +2 -2
  200. package/dist/replay/JavaScriptReplayWorker.js +95 -11
  201. package/dist/replay/JavaScriptReplayWorkerTypes.js +1 -0
  202. package/dist/replay/LinuxJavaScriptReplayRunner.js +10 -15
  203. package/dist/replay/ReplayOutcome.js +26 -15
  204. package/dist/replay/ReplayWorkerProtocol.js +34 -29
  205. package/dist/server/createServer.js +101 -144
  206. package/dist/server/mcpClientMetadata.js +32 -0
  207. package/dist/server/registerApplicationTools/characterization.js +6 -13
  208. package/dist/server/registerApplicationTools/compareExportShapes.js +2 -7
  209. package/dist/server/registerApplicationTools/compareSourceToBundle.js +2 -7
  210. package/dist/server/registerApplicationTools/compareVersions.js +3 -8
  211. package/dist/server/registerApplicationTools/controlledReplay.js +6 -11
  212. package/dist/server/registerApplicationTools/coverage.js +13 -19
  213. package/dist/server/registerApplicationTools/obligationLedger.js +1 -6
  214. package/dist/server/registerApplicationTools/readiness.js +1 -6
  215. package/dist/server/registerApplicationTools/traceFeature.js +3 -8
  216. package/dist/server/registerApplicationTools/traceSemantics.js +1 -6
  217. package/dist/server/registerArtifactComparisonTool.js +12 -16
  218. package/dist/server/registerBrowserScenarioTool.js +1 -6
  219. package/dist/server/registerBrowserTools.js +37 -79
  220. package/dist/server/registerBundleComparisonTool.js +10 -14
  221. package/dist/server/registerCloseLifecycleTool.js +6 -14
  222. package/dist/server/registerElectronTools.js +30 -47
  223. package/dist/server/registerEnhancedTools.js +124 -122
  224. package/dist/server/registerEvidenceResources.js +50 -101
  225. package/dist/server/registerEvidenceTools.js +5 -9
  226. package/dist/server/registerFunctionComparisonTool.js +11 -15
  227. package/dist/server/registerInvestigationTools.js +42 -59
  228. package/dist/server/registerJavaScriptApplicationGraphResource.js +0 -4
  229. package/dist/server/registerJavaScriptRuntimeObservationTools.js +13 -27
  230. package/dist/server/registerManagedWorkflowTools/compareManagedMembers.js +4 -9
  231. package/dist/server/registerManagedWorkflowTools/contract.js +2 -3
  232. package/dist/server/registerManagedWorkflowTools/importManagedReconstruction.js +3 -8
  233. package/dist/server/registerManagedWorkflowTools/planManagedRuntimeCorrelation.js +3 -8
  234. package/dist/server/registerManagedWorkflowTools/projectManagedApplicationGraph.js +8 -13
  235. package/dist/server/registerManagedWorkflowTools/verifyManagedNativeBoundaries.js +4 -9
  236. package/dist/server/registerOfficialTools.js +8 -9
  237. package/dist/server/registerProcessComparisonTool.js +19 -24
  238. package/dist/server/registerReconstructionObligationLedgerResource.js +0 -7
  239. package/dist/server/registerReconstructionReadinessResource.js +0 -7
  240. package/dist/server/registerReplayMachineTool.js +4 -10
  241. package/dist/server/registerSessionRecordTools.js +146 -47
  242. package/dist/server/registerSessionStatusTool.js +14 -29
  243. package/dist/server/registerSessionTools.js +45 -41
  244. package/dist/server/sessionAvailabilityPolicy.js +2 -1
  245. package/dist/server/sessionToolPolicies.js +1 -5
  246. package/dist/server/toolRegistrationOptions.js +4 -177
  247. package/dist/server/toolResult.js +14 -18
  248. package/package.json +35 -16
  249. package/scripts/electron-active-hook-boundaries.cjs +394 -0
  250. package/scripts/electron-active-hook.cjs +501 -0
  251. package/skills/reverse-engineer-anything/SKILL.md +10 -3
  252. package/dist/server/DynamicToolAvailability.js +0 -70
  253. package/dist/server/toolInputValidation.js +0 -77
@@ -0,0 +1,324 @@
1
+ import { z } from "zod";
2
+ import { BrowserObservationError } from "../domain/errors.js";
3
+ import { redactSensitiveText } from "./SensitiveTextCapture.js";
4
+ const OPERATION = "capture_electron_scenario";
5
+ const hookEventSchema = z.strictObject({
6
+ sequence: z.number().int().min(1),
7
+ correlation_id: z.string().max(256).nullable(),
8
+ kind: z.enum([
9
+ "main-handler-invocation",
10
+ "main-event-invocation",
11
+ "utility-process-fork",
12
+ "utility-process-message",
13
+ "ipc-main-to-renderer",
14
+ "ipc-utility-to-main",
15
+ "ipc-renderer-send",
16
+ "ipc-renderer-invoke",
17
+ "ipc-renderer-post-message",
18
+ "app-lifecycle",
19
+ "window-lifecycle",
20
+ "web-contents-lifecycle",
21
+ "navigation",
22
+ "shell-attempt",
23
+ "process-lifecycle",
24
+ "permission",
25
+ "popup-attempt",
26
+ "download",
27
+ "protocol",
28
+ "preload",
29
+ "native-addon",
30
+ "updater",
31
+ "error",
32
+ ]),
33
+ event: z.string().max(128).nullable(),
34
+ phase: z.enum(["attempted", "completed", "blocked", "failed", "observed"]),
35
+ channel: z.string().max(1_024).nullable(),
36
+ channel_truncated: z.boolean().default(false),
37
+ direction: z
38
+ .enum([
39
+ "renderer-to-main",
40
+ "main-to-renderer",
41
+ "main-to-utility",
42
+ "utility-to-main",
43
+ ])
44
+ .nullable(),
45
+ sender: z.string().max(256).nullable(),
46
+ receiver: z.string().max(256).nullable(),
47
+ frame: z.string().max(256).nullable(),
48
+ target: z.string().max(256).nullable(),
49
+ argument_shapes: z.array(z.string().max(64)).max(32),
50
+ argument_shapes_truncated: z.boolean().default(false),
51
+ result_shape: z.string().max(64).nullable(),
52
+ process_type: z.string().max(128).nullable(),
53
+ source: z.string().max(64),
54
+ capture_method: z.enum(["api-wrapper", "event-emitter", "process-hook"]),
55
+ artifact_path: z.string().max(16_384).nullable(),
56
+ artifact_sha256: z
57
+ .string()
58
+ .regex(/^[a-f0-9]{64}$/u)
59
+ .nullable(),
60
+ error: z.boolean(),
61
+ });
62
+ const hookSnapshotSchema = z.strictObject({
63
+ events: z.array(hookEventSchema).max(20_000),
64
+ observed: z.number().int().min(0),
65
+ observed_ipc: z.number().int().min(0),
66
+ observed_runtime: z.number().int().min(0),
67
+ truncated: z.boolean(),
68
+ hook_error: z.boolean(),
69
+ });
70
+ const metricSchema = z.strictObject({
71
+ pid: z.number().int().min(1),
72
+ type: z.string().min(1).max(128),
73
+ name: z.string().max(256).nullable(),
74
+ service_name: z.string().max(256).nullable(),
75
+ });
76
+ const windowMetadataSchema = z.strictObject({
77
+ window_id: z.string().max(256),
78
+ web_contents_id: z.string().max(256),
79
+ visible: z.boolean().nullable(),
80
+ destroyed: z.boolean(),
81
+ });
82
+ /** Run bounded, explicit actions against provider-owned Electron windows. */
83
+ export const runElectronActions = async (application, input, options, deadline) => {
84
+ const actions = [];
85
+ for (const action of input.actions) {
86
+ if (options.signal?.aborted === true)
87
+ throw new BrowserObservationError(OPERATION, "cancelled");
88
+ const actionStartedAt = Date.now();
89
+ const windowIndex = actionWindowIndex(action);
90
+ let selectedWindow;
91
+ try {
92
+ const remaining = deadline - Date.now();
93
+ if (remaining <= 0)
94
+ throw new BrowserObservationError(OPERATION, "timeout");
95
+ selectedWindow =
96
+ windowIndex === null
97
+ ? undefined
98
+ : await waitForWindow({
99
+ application,
100
+ windowIndex,
101
+ timeout: input.limits.action_timeout_ms,
102
+ signal: options.signal,
103
+ deadline,
104
+ });
105
+ if ((action.kind === "click" ||
106
+ action.kind === "renderer-reload" ||
107
+ action.kind === "renderer-crash") &&
108
+ selectedWindow === undefined)
109
+ throw new BrowserObservationError(OPERATION, "window_not_found");
110
+ await runAction({
111
+ application,
112
+ action,
113
+ selectedWindow,
114
+ remaining,
115
+ actionTimeoutMs: input.limits.action_timeout_ms,
116
+ options,
117
+ deadline,
118
+ });
119
+ actions.push({
120
+ step_id: action.step_id,
121
+ kind: action.kind,
122
+ window_index: windowIndex,
123
+ target: selectedWindow === undefined ? null : `window:${String(windowIndex)}`,
124
+ status: "completed",
125
+ elapsed_ms: Date.now() - actionStartedAt,
126
+ error: null,
127
+ });
128
+ }
129
+ catch (cause) {
130
+ actions.push({
131
+ step_id: action.step_id,
132
+ kind: action.kind,
133
+ window_index: windowIndex,
134
+ target: selectedWindow === undefined ? null : `window:${String(windowIndex)}`,
135
+ status: options.signal?.aborted ? "cancelled" : "failed",
136
+ elapsed_ms: Date.now() - actionStartedAt,
137
+ error: safeActionErrorMessage(cause, action),
138
+ });
139
+ break;
140
+ }
141
+ }
142
+ return actions;
143
+ };
144
+ const runAction = async ({ application, action, selectedWindow, remaining, actionTimeoutMs, options, deadline, }) => {
145
+ switch (action.kind) {
146
+ case "click":
147
+ if (selectedWindow === undefined)
148
+ throw new BrowserObservationError(OPERATION, "window_not_found");
149
+ await runWithExecutionLimits(selectedWindow.locator(action.selector).click({
150
+ timeout: Math.min(actionTimeoutMs, remaining),
151
+ }), options.signal, deadline);
152
+ return;
153
+ case "wait": {
154
+ const page = selectedWindow ??
155
+ (await application.firstWindow({
156
+ timeout: Math.min(actionTimeoutMs, remaining),
157
+ }));
158
+ await runWithExecutionLimits(page.waitForTimeout(Math.min(action.duration_ms, remaining)), options.signal, deadline);
159
+ if (action.duration_ms > remaining)
160
+ throw new BrowserObservationError(OPERATION, "timeout");
161
+ return;
162
+ }
163
+ case "renderer-reload":
164
+ if (selectedWindow === undefined)
165
+ throw new BrowserObservationError(OPERATION, "window_not_found");
166
+ await runWithExecutionLimits(selectedWindow.reload({
167
+ timeout: Math.min(actionTimeoutMs, remaining),
168
+ }), options.signal, deadline);
169
+ return;
170
+ case "renderer-crash": {
171
+ const crashed = await runWithExecutionLimits(application.evaluate(({ BrowserWindow }, index) => {
172
+ const candidate = BrowserWindow.getAllWindows()[index];
173
+ if (candidate === undefined || candidate.isDestroyed())
174
+ return false;
175
+ candidate.webContents.forcefullyCrashRenderer();
176
+ return true;
177
+ }, action.window_index), options.signal, deadline);
178
+ if (!crashed)
179
+ throw new BrowserObservationError(OPERATION, "window_not_found");
180
+ return;
181
+ }
182
+ case "deep-link":
183
+ await runWithExecutionLimits(emitDeepLink(application, action), options.signal, deadline);
184
+ return;
185
+ }
186
+ };
187
+ const actionWindowIndex = (action) => {
188
+ switch (action.kind) {
189
+ case "click":
190
+ case "renderer-reload":
191
+ case "renderer-crash":
192
+ return action.window_index;
193
+ case "wait":
194
+ return action.window_index ?? null;
195
+ case "deep-link":
196
+ return null;
197
+ }
198
+ };
199
+ const waitForWindow = async ({ application, windowIndex, timeout, signal, deadline, }) => {
200
+ const stopAt = Math.min(deadline, Date.now() + timeout);
201
+ while (Date.now() < stopAt) {
202
+ if (signal?.aborted === true)
203
+ throw new BrowserObservationError(OPERATION, "cancelled");
204
+ const window = application.windows()[windowIndex];
205
+ if (window !== undefined)
206
+ return window;
207
+ await runWithExecutionLimits(new Promise((resolve) => setTimeout(resolve, 25)), signal, stopAt);
208
+ }
209
+ throw new BrowserObservationError(OPERATION, "window_not_found");
210
+ };
211
+ const emitDeepLink = (application, action) => application.evaluate(({ app }, payload) => {
212
+ if (payload.delivery === "open-url")
213
+ return app.emit("open-url", { preventDefault: () => undefined }, payload.url);
214
+ return app.emit("second-instance", {}, [payload.url], "", {});
215
+ }, { delivery: action.delivery, url: action.url });
216
+ /** Read the current Electron windows, metrics, and bounded hook snapshot. */
217
+ // oxlint-disable-next-line max-lines-per-function -- one state read must share one capture boundary.
218
+ export const readApplicationState = async (application, maxWindows) => {
219
+ const pages = application.windows();
220
+ const pageSnapshotsPromise = Promise.all(pages.slice(0, maxWindows).map(async (page, index) => ({
221
+ index,
222
+ url: page.url().slice(0, 65_536),
223
+ title: (await page.title()).slice(0, 16_384),
224
+ })));
225
+ const windowMetadataPromise = application
226
+ .evaluate(({ BrowserWindow }) => BrowserWindow.getAllWindows().map((window) => ({
227
+ window_id: `window:${String(window.id)}`,
228
+ web_contents_id: `webContents:${String(window.webContents.id)}`,
229
+ visible: window.isDestroyed() ? null : window.isVisible(),
230
+ destroyed: window.isDestroyed(),
231
+ })))
232
+ .then((value) => z.array(windowMetadataSchema).parse(value));
233
+ const processMetricsPromise = application
234
+ .evaluate(({ app }) => app
235
+ .getAppMetrics()
236
+ .map((metric) => ({
237
+ pid: metric.pid,
238
+ type: metric.type,
239
+ name: metric.name ?? null,
240
+ service_name: metric.serviceName ?? null,
241
+ })))
242
+ .then((value) => z.array(metricSchema).parse(value));
243
+ const electronVersionPromise = application
244
+ .evaluate(() => process.versions.electron)
245
+ .then((value) => z.string().parse(value));
246
+ const hookSnapshotPromise = application
247
+ .evaluate(() => {
248
+ const candidate = Reflect.get(globalThis, "__reaElectronActiveSnapshot");
249
+ return typeof candidate === "function"
250
+ ? candidate()
251
+ : {
252
+ events: [],
253
+ observed: 0,
254
+ observed_ipc: 0,
255
+ observed_runtime: 0,
256
+ truncated: false,
257
+ hook_error: true,
258
+ };
259
+ })
260
+ .then((value) => hookSnapshotSchema.parse(value));
261
+ const [pageSnapshots, windowMetadata, processMetrics, electronVersion, hookSnapshot,] = await Promise.all([
262
+ pageSnapshotsPromise,
263
+ windowMetadataPromise,
264
+ processMetricsPromise,
265
+ electronVersionPromise,
266
+ hookSnapshotPromise,
267
+ ]);
268
+ return {
269
+ windows: pageSnapshots.map((page) => {
270
+ const metadata = windowMetadata[page.index];
271
+ if (metadata === undefined)
272
+ throw new BrowserObservationError(OPERATION, "window_metadata_missing");
273
+ return {
274
+ window_id: metadata.window_id,
275
+ web_contents_id: metadata.web_contents_id,
276
+ url: page.url,
277
+ title: page.title,
278
+ visible: metadata.visible,
279
+ destroyed: metadata.destroyed,
280
+ };
281
+ }),
282
+ windowsTruncated: pages.length > maxWindows,
283
+ metrics: processMetrics,
284
+ electronVersion,
285
+ hookSnapshot,
286
+ };
287
+ };
288
+ /** Apply a deadline and caller cancellation to one Playwright operation. */
289
+ export const runWithExecutionLimits = async (operation, signal, deadline) => {
290
+ if (signal?.aborted === true)
291
+ throw new BrowserObservationError(OPERATION, "cancelled");
292
+ let timer;
293
+ let abortListener;
294
+ const timeout = new Promise((_, reject) => {
295
+ timer = setTimeout(() => reject(new BrowserObservationError(OPERATION, "timeout")), Math.max(1, deadline - Date.now()));
296
+ });
297
+ const cancellation = signal === undefined
298
+ ? timeout
299
+ : Promise.race([
300
+ timeout,
301
+ new Promise((_, reject) => {
302
+ abortListener = () => reject(new BrowserObservationError(OPERATION, "cancelled"));
303
+ signal.addEventListener("abort", abortListener, { once: true });
304
+ }),
305
+ ]);
306
+ try {
307
+ return await Promise.race([operation, cancellation]);
308
+ }
309
+ finally {
310
+ if (timer !== undefined)
311
+ clearTimeout(timer);
312
+ if (abortListener !== undefined)
313
+ signal?.removeEventListener("abort", abortListener);
314
+ }
315
+ };
316
+ const safeActionErrorMessage = (cause, action) => {
317
+ let message = cause instanceof Error ? cause.message : "Electron action failed";
318
+ const inputValues = Object.values(action)
319
+ .filter((value) => typeof value === "string" && value.length > 0)
320
+ .sort((left, right) => right.length - left.length);
321
+ for (const value of inputValues)
322
+ message = message.split(value).join("<redacted-input>");
323
+ return redactSensitiveText(message).slice(0, 1_024);
324
+ };
@@ -0,0 +1,294 @@
1
+ import { realpath } from "node:fs/promises";
2
+ import { randomUUID } from "node:crypto";
3
+ import { fileURLToPath } from "node:url";
4
+ import { _electron as electron } from "playwright-core";
5
+ import { electronActiveObservationResultSchema, } from "../domain/electronActiveObservation.js";
6
+ import { AnalysisError, BrowserObservationError, ProviderAdapterError, } from "../domain/errors.js";
7
+ import { isPathContained } from "../domain/permissionPolicy.js";
8
+ import { err, ok } from "../domain/result.js";
9
+ import { cleanupOwnedProcessGroup, cleanupWindowsProcessTree, observeOwnedProcessLineage, selectCapturedProcessGroupIds, verifyNoTokenOwnedProcesses, } from "../process/ProcessOwnership.js";
10
+ import { runElectronActions, readApplicationState, runWithExecutionLimits, } from "./PlaywrightElectronActiveActions.js";
11
+ const OPERATION = "capture_electron_scenario";
12
+ /** Public identity for provider-owned Electron runtime experiments. */
13
+ export const PLAYWRIGHT_ELECTRON_ACTIVE_PROVIDER_IDENTITY = Object.freeze({
14
+ id: "rea-playwright-electron-active",
15
+ name: "REA Playwright active Electron observation provider",
16
+ version: "1",
17
+ });
18
+ const hookPath = fileURLToPath(new URL("../../scripts/electron-active-hook.cjs", import.meta.url));
19
+ const observableEventFamilies = [
20
+ "app-lifecycle",
21
+ "window-lifecycle",
22
+ "web-contents-lifecycle",
23
+ "navigation",
24
+ "shell-attempt",
25
+ "process-lifecycle",
26
+ "permission",
27
+ "popup-attempt",
28
+ "download",
29
+ "protocol",
30
+ "preload",
31
+ "preload-configuration",
32
+ "renderer-ipc",
33
+ "native-addon",
34
+ "updater",
35
+ "error",
36
+ "ipc",
37
+ ];
38
+ const ipcEventKinds = new Set([
39
+ "main-handler-invocation",
40
+ "main-event-invocation",
41
+ "utility-process-fork",
42
+ "utility-process-message",
43
+ "ipc-main-to-renderer",
44
+ "ipc-utility-to-main",
45
+ "ipc-renderer-send",
46
+ "ipc-renderer-invoke",
47
+ "ipc-renderer-post-message",
48
+ ]);
49
+ const createCoverage = (hookSnapshot) => {
50
+ const observedEventFamilies = [
51
+ ...new Set(hookSnapshot.events.map(({ kind, process_type }) => {
52
+ if (kind === "preload" && process_type === "main")
53
+ return "preload-configuration";
54
+ if (kind.startsWith("ipc-renderer") && process_type !== "renderer")
55
+ return "ipc";
56
+ return ipcEventKinds.has(kind) ? "ipc" : kind;
57
+ })),
58
+ ].sort();
59
+ const unavailableEventFamilies = new Set(observableEventFamilies.filter((family) => !observedEventFamilies.includes(family)));
60
+ const observedRoles = [
61
+ ...new Set(hookSnapshot.events.flatMap(({ process_type, kind }) => [
62
+ ...(process_type === null ? [] : [process_type]),
63
+ ...(kind === "window-lifecycle" ? ["window"] : []),
64
+ ...(kind === "web-contents-lifecycle" || kind === "navigation"
65
+ ? ["web_contents"]
66
+ : []),
67
+ ...(kind === "preload" && process_type === "preload"
68
+ ? ["preload"]
69
+ : []),
70
+ ...(kind.startsWith("ipc-renderer") && process_type === "renderer"
71
+ ? ["renderer"]
72
+ : []),
73
+ ])),
74
+ ].sort();
75
+ const rendererContextObserved = hookSnapshot.events.some(({ process_type }) => process_type === "renderer" || process_type === "preload");
76
+ if (!rendererContextObserved) {
77
+ unavailableEventFamilies.add("preload");
78
+ unavailableEventFamilies.add("renderer-ipc");
79
+ }
80
+ return {
81
+ status: hookSnapshot.hook_error
82
+ ? "hook_conflict"
83
+ : hookSnapshot.truncated
84
+ ? "capture_truncated"
85
+ : "partial_attach",
86
+ observed_event_families: observedEventFamilies,
87
+ unavailable_event_families: [...unavailableEventFamilies].sort(),
88
+ observed_roles: observedRoles,
89
+ pre_capture_activity: "unavailable",
90
+ };
91
+ };
92
+ /** Launch an owned Electron application through the official Playwright API. */
93
+ export class PlaywrightElectronActiveProvider {
94
+ identity() {
95
+ return PLAYWRIGHT_ELECTRON_ACTIVE_PROVIDER_IDENTITY;
96
+ }
97
+ async capture(input, options = {}) {
98
+ let application;
99
+ let ownership;
100
+ let lineage;
101
+ let outcome;
102
+ const runId = randomUUID();
103
+ try {
104
+ if (options.signal?.aborted === true)
105
+ throw new BrowserObservationError(OPERATION, "cancelled");
106
+ const deadline = Date.now() + input.limits.max_duration_ms;
107
+ const paths = await canonicalPaths(input);
108
+ application = await electron.launch({
109
+ executablePath: paths.executable,
110
+ cwd: paths.root,
111
+ env: safeElectronEnvironment(input.limits.max_ipc_events, input.limits.max_runtime_events, runId),
112
+ args: ["-r", hookPath, paths.application, ...input.args],
113
+ timeout: Math.max(1, deadline - Date.now()),
114
+ });
115
+ const leaderPid = application.process().pid;
116
+ if (typeof leaderPid !== "number" ||
117
+ !Number.isSafeInteger(leaderPid) ||
118
+ leaderPid <= 0)
119
+ throw new BrowserObservationError(OPERATION, "process_ownership_unavailable");
120
+ ownership = {
121
+ runId,
122
+ leaderPid,
123
+ processGroupId: leaderPid,
124
+ expectedParentPid: process.pid,
125
+ expectedCommand: paths.executable,
126
+ sweepTokenOwnedProcesses: true,
127
+ };
128
+ const actions = await runElectronActions(application, input, options, deadline);
129
+ const state = await runWithExecutionLimits(readApplicationState(application, input.limits.max_windows), options.signal, deadline);
130
+ outcome = ok(createResult(paths, input, actions, state));
131
+ }
132
+ catch (cause) {
133
+ outcome = err(providerError(cause));
134
+ }
135
+ if (application !== undefined) {
136
+ if (ownership !== undefined)
137
+ lineage = await observeOwnedProcessLineage(ownership);
138
+ let closeError;
139
+ try {
140
+ await runWithExecutionLimits(application.close(), undefined, Date.now() + 5_000);
141
+ }
142
+ catch (cause) {
143
+ closeError = cause;
144
+ }
145
+ const cleanup = ownership === undefined
146
+ ? {
147
+ cleaned: false,
148
+ reason: "owned Electron process identity was unavailable",
149
+ }
150
+ : await cleanupElectronProcesses(ownership, lineage);
151
+ if (!cleanup.cleaned)
152
+ return err(providerError(new BrowserObservationError(OPERATION, "cleanup_failed", {
153
+ cause: new Error(cleanup.reason),
154
+ })));
155
+ if (closeError !== undefined)
156
+ return err(providerError(closeError));
157
+ }
158
+ return outcome;
159
+ }
160
+ }
161
+ const cleanupElectronProcesses = async (ownership, lineage) => {
162
+ if (process.platform === "win32") {
163
+ const root = await cleanupWindowsProcessTree(ownership.leaderPid);
164
+ if (!root.cleaned)
165
+ return root;
166
+ if (lineage?.status !== "verified")
167
+ return {
168
+ cleaned: false,
169
+ reason: "owned Electron lineage was unavailable; helper cleanup was not proven",
170
+ };
171
+ for (const descendant of lineage.lineage.descendants) {
172
+ const result = await cleanupWindowsProcessTree(descendant.pid);
173
+ if (!result.cleaned)
174
+ return result;
175
+ }
176
+ return root;
177
+ }
178
+ if (lineage?.status !== "verified") {
179
+ const root = await cleanupOwnedProcessGroup(ownership);
180
+ return root.cleaned
181
+ ? {
182
+ cleaned: false,
183
+ reason: "owned Electron lineage was unavailable; helper cleanup was not proven",
184
+ }
185
+ : root;
186
+ }
187
+ const groupIds = selectCapturedProcessGroupIds(ownership.leaderPid, lineage.lineage.descendants.map(({ pid, processGroupId }) => ({
188
+ pid,
189
+ process_group_id: processGroupId,
190
+ })));
191
+ let signaled = false;
192
+ for (const processGroupId of groupIds) {
193
+ const cleanupOwnership = processGroupId === ownership.processGroupId
194
+ ? { ...ownership, leaderPid: processGroupId, processGroupId }
195
+ : {
196
+ runId: ownership.runId,
197
+ leaderPid: processGroupId,
198
+ processGroupId,
199
+ };
200
+ const result = await cleanupOwnedProcessGroup(cleanupOwnership);
201
+ if (!result.cleaned)
202
+ return result;
203
+ signaled ||= result.signaled;
204
+ }
205
+ const remaining = await verifyNoTokenOwnedProcesses(ownership.runId);
206
+ if (!remaining.cleaned)
207
+ return remaining;
208
+ return { cleaned: true, signaled };
209
+ };
210
+ const createResult = (paths, input, actions, state) => {
211
+ const { hookSnapshot } = state;
212
+ const ipcEvents = hookSnapshot.events.filter(({ kind }) => ipcEventKinds.has(kind));
213
+ return electronActiveObservationResultSchema.parse({
214
+ schema_version: 1,
215
+ application: {
216
+ executable_path: paths.executable,
217
+ application_path: paths.application,
218
+ electron_version: state.electronVersion,
219
+ process_ownership: "provider-owned",
220
+ cleanup: "terminated-owned-process",
221
+ },
222
+ actions,
223
+ windows: state.windows.slice(0, input.limits.max_windows),
224
+ windows_truncated: state.windowsTruncated,
225
+ processes: {
226
+ items: state.metrics.slice(0, input.limits.max_processes),
227
+ truncated: state.metrics.length > input.limits.max_processes,
228
+ },
229
+ ipc: {
230
+ events: ipcEvents.slice(0, input.limits.max_ipc_events),
231
+ observed: hookSnapshot.observed_ipc,
232
+ retained: Math.min(ipcEvents.length, input.limits.max_ipc_events),
233
+ truncated: hookSnapshot.truncated ||
234
+ ipcEvents.length > input.limits.max_ipc_events,
235
+ },
236
+ timeline: {
237
+ events: hookSnapshot.events.slice(0, input.limits.max_runtime_events),
238
+ observed: hookSnapshot.observed,
239
+ retained: Math.min(hookSnapshot.events.length, input.limits.max_runtime_events),
240
+ truncated: hookSnapshot.truncated ||
241
+ hookSnapshot.events.length > input.limits.max_runtime_events,
242
+ },
243
+ coverage: createCoverage(hookSnapshot),
244
+ limitations: [
245
+ "IPC payloads are represented only by bounded value shapes; values are never retained, channels are capped at 1,024 characters, and argument-shape arrays at 32 entries.",
246
+ "IPC direction and sender/receiver identifiers are observed only where Electron exposes them at the hooked boundary.",
247
+ "The runtime timeline records bounded lifecycle, navigation, shell, permission, popup, download, protocol, preload, native-addon, process, and IPC events; activity before hook installation is unavailable.",
248
+ "The preload and renderer process contexts are not instrumented by the main-process -r hook; preload configuration and contextBridge API-shape events are main-boundary observations, not proof of renderer-side execution.",
249
+ "Process metrics are an Electron API snapshot and do not prove hostile-local-user isolation.",
250
+ "External shell opens, external navigation, permission grants, downloads, popup windows, updater relaunches, and OS integration are blocked and recorded by the active hook; other application filesystem and network behavior is not sandboxed by this provider.",
251
+ ...(state.windowsTruncated
252
+ ? ["Windows beyond max_windows were not retained."]
253
+ : []),
254
+ ...(hookSnapshot.hook_error
255
+ ? ["The active IPC hook could not be installed."]
256
+ : []),
257
+ ],
258
+ });
259
+ };
260
+ const canonicalPaths = async (input) => {
261
+ const [executable, application, root] = await Promise.all([
262
+ realpath(input.executable_path),
263
+ realpath(input.application_path),
264
+ realpath(input.application_root),
265
+ ]);
266
+ if (!isPathContained(root, application))
267
+ throw new BrowserObservationError(OPERATION, "target_not_allowed");
268
+ return { executable, application, root };
269
+ };
270
+ const safeElectronEnvironment = (maxIpcEvents, maxRuntimeEvents, runId) => Object.fromEntries([
271
+ ...[
272
+ "HOME",
273
+ "LANG",
274
+ "PATH",
275
+ "TMP",
276
+ "TMPDIR",
277
+ "USER",
278
+ "DISPLAY",
279
+ "WAYLAND_DISPLAY",
280
+ ]
281
+ .map((name) => [name, process.env[name]])
282
+ .filter((entry) => entry[1] !== undefined),
283
+ ["REA_ELECTRON_ACTIVE_MAX_IPC_EVENTS", String(maxIpcEvents)],
284
+ [
285
+ "REA_ELECTRON_ACTIVE_MAX_RUNTIME_EVENTS",
286
+ String(maxRuntimeEvents),
287
+ ],
288
+ ["REA_PROCESS_RUN_ID", runId],
289
+ ]);
290
+ const providerError = (cause) => cause instanceof AnalysisError
291
+ ? cause
292
+ : new ProviderAdapterError(PLAYWRIGHT_ELECTRON_ACTIVE_PROVIDER_IDENTITY.id, OPERATION, {
293
+ cause,
294
+ });
@@ -89,9 +89,9 @@ export const openPlaywrightScenarioBrowser = async (scenario, environment, maxim
89
89
  await configureAttachedEnvironment(target.context, target.page, scenario);
90
90
  return { ...target, browser, profilePath: undefined };
91
91
  }
92
- catch (error) {
92
+ catch (cause) {
93
93
  await browser.close();
94
- throw error;
94
+ throw cause;
95
95
  }
96
96
  }
97
97
  const profilePath = await mkdtemp(join(tmpdir(), "rea-browser-scenario-"));
@@ -125,9 +125,9 @@ export const openPlaywrightScenarioBrowser = async (scenario, environment, maxim
125
125
  const page = context.pages()[0] ?? (await context.newPage());
126
126
  return { context, page, browser, profilePath };
127
127
  }
128
- catch (error) {
128
+ catch (cause) {
129
129
  await rm(profilePath, { recursive: true, force: true, maxRetries: 3 });
130
- throw error;
130
+ throw cause;
131
131
  }
132
132
  };
133
133
  export const closePlaywrightScenarioBrowser = async ({ context, browser, profilePath, }) => {
@@ -201,11 +201,11 @@ export class PlaywrightScenarioSession {
201
201
  try {
202
202
  opened = await withPlaywrightExecutionBoundary(() => opening, remaining(), options.signal);
203
203
  }
204
- catch (error) {
204
+ catch (cause) {
205
205
  void opening
206
206
  .then((lateOpened) => closePlaywrightScenarioBrowser(lateOpened))
207
207
  .catch(() => undefined);
208
- throw error;
208
+ throw cause;
209
209
  }
210
210
  try {
211
211
  await withPlaywrightExecutionBoundary(() => initializePage(opened.context, opened.page, scenario, secrets), remaining(), options.signal);
@@ -224,9 +224,9 @@ export class PlaywrightScenarioSession {
224
224
  }), remaining(), options.signal);
225
225
  return session;
226
226
  }
227
- catch (error) {
227
+ catch (cause) {
228
228
  await closePlaywrightScenarioBrowser(opened);
229
- throw error;
229
+ throw cause;
230
230
  }
231
231
  }
232
232
  currentUrl() {
@@ -1,4 +1,4 @@
1
- const SECRET_ASSIGNMENT = /\b(authorization|api[_-]?key|access[_-]?token|refresh[_-]?token|password|passwd|secret|cookie|session)\b\s*[:=]\s*(?:Bearer\s+[A-Za-z0-9._~+/=-]+|"[^"]*"|'[^']*'|[^\s,;]+)/giu;
1
+ const SECRET_ASSIGNMENT = /\b(authorization|api[_-]?key|access[_-]?token|refresh[_-]?token|token|password|passwd|secret|cookie|session)\b\s*[:=]\s*(?:Bearer\s+[A-Za-z0-9._~+/=-]+|"[^"]*"|'[^']*'|[^\s,;]+)/giu;
2
2
  const BEARER = /\bBearer\s+[A-Za-z0-9._~+/=-]+/giu;
3
3
  const JWT = /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/gu;
4
4
  /** Redact credential-shaped substrings before approved console text is retained. */
@@ -11,6 +11,7 @@ export const describeInspectorTargetLimitations = () => [
11
11
  "REA attaches to an already-running exact target and never launches, resumes, evaluates, pauses, or mutates it.",
12
12
  "Only Runtime.enable and Debugger.enable are sent; source text, object values, EventEmitter activity, and Electron IPC are not inspected.",
13
13
  "Target IDs and locations are authorized, but the Inspector protocol does not authenticate an operating-system process ID or Electron role.",
14
+ "Some Electron main targets report only file://; for those targets, the first approved file root is recorded as a scope fallback and script locations are authorized independently.",
14
15
  "No runtime graph depth is traversed because passive Inspector events do not establish require/import caller edges.",
15
16
  ];
16
17
  /** Canonically authorize, deduplicate, and sort one bounded raw capture. */