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,300 @@
1
+ import {} from "playwright-core";
2
+ import { BrowserObservationError } from "../domain/errors.js";
3
+ import { BrowserScenarioSecrets } from "./BrowserScenarioSecrets.js";
4
+ import { closePlaywrightScenarioBrowser, openPlaywrightScenarioBrowser, } from "./PlaywrightScenarioBrowser.js";
5
+ import { performPlaywrightScenarioAction } from "./PlaywrightScenarioActions.js";
6
+ import { BrowserScenarioCaptureBudget, capturePlaywrightStepArtifacts, } from "./PlaywrightScenarioArtifacts.js";
7
+ import { PlaywrightScenarioEvents } from "./PlaywrightScenarioEvents.js";
8
+ import { withPlaywrightExecutionBoundary } from "./PlaywrightExecutionBoundary.js";
9
+ const OPERATION = "capture_browser_scenario";
10
+ const originAllowed = (value, allowedOrigins) => {
11
+ try {
12
+ return allowedOrigins.has(new URL(value).origin);
13
+ }
14
+ catch {
15
+ return false;
16
+ }
17
+ };
18
+ const storageSeeds = (blocks, secrets) => {
19
+ const seeds = {};
20
+ for (const { origin, entries } of blocks)
21
+ (seeds[origin] ??= []).push(...entries.map(({ name, value }) => [name, secrets.value(value)]));
22
+ return seeds;
23
+ };
24
+ const installStorageSeeds = async (context, page, scenario, secrets) => {
25
+ await context.addCookies(scenario.storage.cookies.map((cookie) => ({
26
+ name: cookie.name,
27
+ value: secrets.value(cookie.value),
28
+ url: secrets.url(cookie.destination),
29
+ httpOnly: cookie.http_only,
30
+ secure: cookie.secure,
31
+ sameSite: cookie.same_site,
32
+ })));
33
+ const storage = {
34
+ local: storageSeeds(scenario.storage.local_storage, secrets),
35
+ session: storageSeeds(scenario.storage.session_storage, secrets),
36
+ };
37
+ const payload = JSON.stringify(storage).replaceAll("<", "\\u003c");
38
+ await page.addInitScript(`(() => {
39
+ const seeds = ${payload};
40
+ const local = seeds.local[window.location.origin] ?? [];
41
+ const session = seeds.session[window.location.origin] ?? [];
42
+ for (const [name, value] of local) window.localStorage.setItem(name, value);
43
+ for (const [name, value] of session)
44
+ window.sessionStorage.setItem(name, value);
45
+ })()`);
46
+ };
47
+ const installHttpPolicy = async (input) => {
48
+ const { context, scenario, secrets, ownedPages, claimOwnedPage } = input;
49
+ const routes = scenario.request_replay.mode === "exact"
50
+ ? new Map(scenario.request_replay.routes.map((route) => [
51
+ `${route.method} ${secrets.url(route.request)}`,
52
+ route,
53
+ ]))
54
+ : new Map();
55
+ await context.route("**/*", async (route, request) => {
56
+ let requestPage;
57
+ try {
58
+ requestPage = request.frame().page();
59
+ }
60
+ catch {
61
+ requestPage = undefined;
62
+ }
63
+ if (scenario.browser.mode === "connect" &&
64
+ requestPage !== undefined &&
65
+ !ownedPages.has(requestPage))
66
+ await claimOwnedPage(requestPage);
67
+ if (scenario.browser.mode === "connect" &&
68
+ (requestPage === undefined || !ownedPages.has(requestPage))) {
69
+ await route.continue();
70
+ return;
71
+ }
72
+ if (!originAllowed(request.url(), new Set(scenario.allowed_origins))) {
73
+ await route.abort("blockedbyclient");
74
+ return;
75
+ }
76
+ const replay = routes.get(`${request.method()} ${request.url()}`);
77
+ if (replay !== undefined) {
78
+ await fulfillReplayRoute(route, replay.response, secrets);
79
+ return;
80
+ }
81
+ if (scenario.request_replay.mode === "exact" &&
82
+ scenario.request_replay.unmatched === "abort") {
83
+ await route.abort("blockedbyclient");
84
+ return;
85
+ }
86
+ await route.continue();
87
+ });
88
+ };
89
+ const fulfillReplayRoute = async (route, response, secrets) => {
90
+ if (response.kind === "redirect") {
91
+ await route.fulfill({
92
+ status: response.status,
93
+ headers: { location: secrets.url(response.destination) },
94
+ });
95
+ return;
96
+ }
97
+ await route.fulfill({
98
+ status: response.status,
99
+ headers: Object.fromEntries(response.headers.map(({ name, value }) => [name, secrets.value(value)])),
100
+ ...(response.body === undefined
101
+ ? {}
102
+ : { body: secrets.value(response.body) }),
103
+ });
104
+ };
105
+ const installWebSocketPolicy = async (owner, scenario) => {
106
+ const allowed = new Set(scenario.allowed_origins);
107
+ await owner.routeWebSocket("**/*", async (route) => {
108
+ const url = new URL(route.url());
109
+ const httpOrigin = `${url.protocol === "wss:" ? "https:" : "http:"}//${url.host}`;
110
+ if (!allowed.has(httpOrigin) ||
111
+ (scenario.request_replay.mode === "exact" &&
112
+ scenario.request_replay.unmatched === "abort")) {
113
+ await route.close({ code: 1008, reason: "outside scenario policy" });
114
+ return;
115
+ }
116
+ route.connectToServer();
117
+ });
118
+ };
119
+ const blockAttachedServiceWorkers = async (page) => {
120
+ const controller = await page.evaluate("Boolean(navigator.serviceWorker?.controller)");
121
+ if (controller)
122
+ throw new BrowserObservationError(OPERATION, "target_not_allowed");
123
+ const blockRegistration = `(() => {
124
+ if (!("serviceWorker" in navigator)) return;
125
+ void navigator.serviceWorker.getRegistrations()
126
+ .then((registrations) => Promise.all(registrations.map((item) => item.unregister())));
127
+ Object.defineProperty(ServiceWorkerContainer.prototype, "register", {
128
+ configurable: false,
129
+ value: () => Promise.reject(new Error("service workers are blocked by scenario policy"))
130
+ });
131
+ })()`;
132
+ await page.addInitScript(blockRegistration);
133
+ await page.evaluate(blockRegistration);
134
+ };
135
+ const initializePage = async (context, page, scenario, secrets) => {
136
+ context.setDefaultTimeout(scenario.limits.action_timeout_ms);
137
+ context.setDefaultNavigationTimeout(scenario.limits.navigation_timeout_ms);
138
+ const ownedPages = new Set([page]);
139
+ if (scenario.browser.mode === "connect")
140
+ await blockAttachedServiceWorkers(page);
141
+ await installWebSocketPolicy(scenario.browser.mode === "launch" ? context : page, scenario);
142
+ const claimOwnedPage = async (candidate) => {
143
+ if (ownedPages.has(candidate))
144
+ return true;
145
+ const opener = await candidate.opener();
146
+ if (opener === null || !ownedPages.has(opener))
147
+ return false;
148
+ await installWebSocketPolicy(candidate, scenario);
149
+ ownedPages.add(candidate);
150
+ return true;
151
+ };
152
+ await installHttpPolicy({
153
+ context,
154
+ scenario,
155
+ secrets,
156
+ ownedPages,
157
+ claimOwnedPage,
158
+ });
159
+ page.on("popup", (popup) => {
160
+ if (scenario.browser.mode === "connect")
161
+ void claimOwnedPage(popup).catch(async () => {
162
+ await popup.close().catch(() => undefined);
163
+ });
164
+ else
165
+ ownedPages.add(popup);
166
+ });
167
+ await installStorageSeeds(context, page, scenario, secrets);
168
+ return ownedPages;
169
+ };
170
+ export class PlaywrightScenarioSession {
171
+ opened;
172
+ scenario;
173
+ secrets;
174
+ eventCapture;
175
+ mode;
176
+ processOwnership;
177
+ product = "Chromium";
178
+ version;
179
+ initialUrl;
180
+ closed = false;
181
+ constructor(opened, scenario, secrets, eventCapture) {
182
+ this.opened = opened;
183
+ this.scenario = scenario;
184
+ this.secrets = secrets;
185
+ this.eventCapture = eventCapture;
186
+ this.mode = scenario.browser.mode;
187
+ this.processOwnership =
188
+ scenario.browser.mode === "launch" ? "provider-owned" : "external";
189
+ this.version = opened.browser.version();
190
+ this.initialUrl = opened.page.url();
191
+ }
192
+ static async open(scenario, environment, options) {
193
+ const remaining = () => options.deadlineAt - Date.now();
194
+ if (options.signal?.aborted === true)
195
+ throw new BrowserObservationError(OPERATION, "cancelled");
196
+ const secrets = BrowserScenarioSecrets.resolve(scenario, environment);
197
+ if (secrets === undefined)
198
+ throw new BrowserObservationError(OPERATION, "secret_unavailable");
199
+ const opening = openPlaywrightScenarioBrowser(scenario, environment, remaining());
200
+ let opened;
201
+ try {
202
+ opened = await withPlaywrightExecutionBoundary(() => opening, remaining(), options.signal);
203
+ }
204
+ catch (error) {
205
+ void opening
206
+ .then((lateOpened) => closePlaywrightScenarioBrowser(lateOpened))
207
+ .catch(() => undefined);
208
+ throw error;
209
+ }
210
+ try {
211
+ await withPlaywrightExecutionBoundary(() => initializePage(opened.context, opened.page, scenario, secrets), remaining(), options.signal);
212
+ const events = new PlaywrightScenarioEvents({
213
+ page: opened.page,
214
+ enabled: new Set(scenario.capture.events),
215
+ limits: scenario.limits,
216
+ secrets,
217
+ budget: options.budget,
218
+ allowedOrigins: scenario.allowed_origins,
219
+ });
220
+ const session = new PlaywrightScenarioSession(opened, scenario, secrets, events);
221
+ await withPlaywrightExecutionBoundary(() => opened.page.goto(secrets.url(scenario.start_url), {
222
+ waitUntil: "load",
223
+ timeout: Math.min(scenario.limits.navigation_timeout_ms, remaining()),
224
+ }), remaining(), options.signal);
225
+ return session;
226
+ }
227
+ catch (error) {
228
+ await closePlaywrightScenarioBrowser(opened);
229
+ throw error;
230
+ }
231
+ }
232
+ currentUrl() {
233
+ return this.opened.page.url();
234
+ }
235
+ setStep(index) {
236
+ this.eventCapture.setStep(index);
237
+ }
238
+ nextEventSequence() {
239
+ return this.eventCapture.nextSequence();
240
+ }
241
+ lastEventSequence() {
242
+ return this.eventCapture.lastSequence();
243
+ }
244
+ events() {
245
+ return this.eventCapture.result();
246
+ }
247
+ eventTruncationSections() {
248
+ return this.eventCapture.truncationSections();
249
+ }
250
+ async perform(action, maximumTimeoutMs, signal) {
251
+ await withPlaywrightExecutionBoundary(() => performPlaywrightScenarioAction({
252
+ page: this.opened.page,
253
+ action,
254
+ secrets: this.secrets,
255
+ defaultTimeoutMs: this.scenario.limits.action_timeout_ms,
256
+ maximumTimeoutMs,
257
+ }), maximumTimeoutMs, signal);
258
+ }
259
+ async capture(requested, budget, maximumTimeoutMs, signal) {
260
+ return withPlaywrightExecutionBoundary(() => capturePlaywrightStepArtifacts({
261
+ context: this.opened.context,
262
+ page: this.opened.page,
263
+ scenario: this.scenario,
264
+ secrets: this.secrets,
265
+ requested,
266
+ budget,
267
+ }), maximumTimeoutMs, signal);
268
+ }
269
+ async close() {
270
+ if (this.closed)
271
+ return this.mode === "launch"
272
+ ? "terminated-owned-process"
273
+ : "disconnected-external";
274
+ this.closed = true;
275
+ await closePlaywrightScenarioBrowser(this.opened);
276
+ return this.mode === "launch"
277
+ ? "terminated-owned-process"
278
+ : "disconnected-external";
279
+ }
280
+ redactError(error) {
281
+ return this.secrets
282
+ .redact(error instanceof Error ? error.message : "browser action failed")
283
+ .slice(0, 4_096);
284
+ }
285
+ }
286
+ /** Production Playwright/CDP session factory. */
287
+ export class PlaywrightScenarioSessionFactory {
288
+ environment;
289
+ constructor(environment = process.env) {
290
+ this.environment = environment;
291
+ }
292
+ open(scenario, options = {}) {
293
+ return PlaywrightScenarioSession.open(scenario, this.environment, {
294
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
295
+ budget: options.budget ??
296
+ new BrowserScenarioCaptureBudget(scenario.limits.max_screenshots, scenario.limits.max_total_metadata_bytes),
297
+ deadlineAt: options.deadlineAt ?? Date.now() + scenario.limits.max_duration_ms,
298
+ });
299
+ }
300
+ }
@@ -0,0 +1,110 @@
1
+ import { createHash } from "node:crypto";
2
+ import { authorizeRuntimeLocation } from "./JavaScriptRuntimeScope.js";
3
+ /** Empty durable-location exclusion counters. */
4
+ export const createInspectorExclusionCounts = () => ({
5
+ outside_file_roots: 0,
6
+ outside_origins: 0,
7
+ unsupported_location: 0,
8
+ });
9
+ /** Stable safety and protocol limitations shared by target-list Evidence. */
10
+ export const describeInspectorTargetLimitations = () => [
11
+ "REA attaches to an already-running exact target and never launches, resumes, evaluates, pauses, or mutates it.",
12
+ "Only Runtime.enable and Debugger.enable are sent; source text, object values, EventEmitter activity, and Electron IPC are not inspected.",
13
+ "Target IDs and locations are authorized, but the Inspector protocol does not authenticate an operating-system process ID or Electron role.",
14
+ "No runtime graph depth is traversed because passive Inspector events do not establish require/import caller edges.",
15
+ ];
16
+ /** Canonically authorize, deduplicate, and sort one bounded raw capture. */
17
+ export const finalizeInspectorCapture = async ({ input, runtime, target, roots, state, }) => {
18
+ const exclusions = createInspectorExclusionCounts();
19
+ const scripts = new Map();
20
+ for (const draft of state.scripts) {
21
+ const decision = await authorizeRuntimeLocation(draft.rawUrl, roots, input.allowed_origins);
22
+ if (!decision.allowed) {
23
+ exclusions[decision.reason] += 1;
24
+ continue;
25
+ }
26
+ const script = scriptFromDraft(draft, decision.location);
27
+ scripts.set(script.script_key, script);
28
+ }
29
+ const items = [...scripts.values()].sort((left, right) => left.script_key < right.script_key
30
+ ? -1
31
+ : left.script_key > right.script_key
32
+ ? 1
33
+ : 0);
34
+ const contexts = [...state.contexts.values()]
35
+ .map((context) => ({
36
+ context_key: context.contextKey,
37
+ state: context.state,
38
+ name: null,
39
+ origin: context.origin,
40
+ }))
41
+ .sort((left, right) => left.context_key < right.context_key
42
+ ? -1
43
+ : left.context_key > right.context_key
44
+ ? 1
45
+ : 0);
46
+ return {
47
+ schema_version: 1,
48
+ runtime,
49
+ target: {
50
+ target_id: target.id,
51
+ protocol_type: target.type,
52
+ attached: target.attached,
53
+ location: target.location,
54
+ runtime_kind: input.runtime_kind,
55
+ runtime_kind_authority: "caller-declared-unverified",
56
+ },
57
+ capture: {
58
+ observation_ms: input.observation_ms,
59
+ events_observed: state.eventsObserved,
60
+ events_retained: state.eventsRetained,
61
+ events_dropped: state.eventsDropped,
62
+ metadata_bytes_retained: state.metadataBytes,
63
+ truncated: state.truncated,
64
+ truncation_reasons: [...state.truncationReasons].sort(),
65
+ },
66
+ scripts: {
67
+ items,
68
+ observed_total: state.scriptsObserved,
69
+ excluded: {
70
+ ...exclusions,
71
+ invalid_protocol_value: state.invalidScripts,
72
+ },
73
+ },
74
+ execution_contexts: contexts,
75
+ directly_observed: [
76
+ "Debugger.scriptParsed established script presence within the bounded capture window.",
77
+ "Runtime execution-context lifecycle events established context creation, destruction, or clearing.",
78
+ ],
79
+ unavailable_without_instrumentation: [
80
+ "require/import caller-to-callee edges",
81
+ "EventEmitter emissions and listener invocation",
82
+ "Electron IPC messages and handlers",
83
+ "script unload events",
84
+ ],
85
+ unknowns: [
86
+ "Scripts collected before attachment may have been garbage-collected and therefore omitted.",
87
+ "A bounded observation window cannot establish that an unobserved script or behavior never occurs.",
88
+ "The declared Node/Electron process role is not authenticated by the Inspector protocol.",
89
+ ],
90
+ limitations: describeInspectorTargetLimitations(),
91
+ };
92
+ };
93
+ const scriptFromDraft = (draft, location) => {
94
+ const stable = JSON.stringify({
95
+ location,
96
+ execution_context_key: draft.executionContextKey,
97
+ cdp_hash: draft.cdpHash,
98
+ length: draft.length,
99
+ is_module: draft.isModule,
100
+ });
101
+ return {
102
+ script_key: `v8_script_${createHash("sha256").update(stable).digest("hex")}`,
103
+ location,
104
+ execution_context_key: draft.executionContextKey,
105
+ cdp_hash: draft.cdpHash,
106
+ length: draft.length,
107
+ is_module: draft.isModule,
108
+ status: "observed-loaded",
109
+ };
110
+ };
@@ -0,0 +1,72 @@
1
+ import { z } from "zod";
2
+ import { BrowserObservationError, } from "../domain/errors.js";
3
+ import { parseCdpEndpointValue, readBoundedCdpJson } from "./CdpEndpoint.js";
4
+ const MAX_VERSION_BYTES = 64 * 1_024;
5
+ const MAX_TARGET_LIST_BYTES = 2 * 1_024 * 1_024;
6
+ const versionSchema = z
7
+ .object({
8
+ Browser: z.string().min(1).max(1_024),
9
+ "Protocol-Version": z.string().min(1).max(100),
10
+ "V8-Version": z.string().max(1_024).optional(),
11
+ })
12
+ .passthrough();
13
+ const targetSchema = z
14
+ .object({
15
+ id: z.string().min(1).max(256),
16
+ type: z.string().min(1).max(100),
17
+ url: z.string().max(65_536),
18
+ attached: z.boolean().default(false),
19
+ webSocketDebuggerUrl: z.string().min(1).max(2_048),
20
+ })
21
+ .passthrough();
22
+ const targetsSchema = z.array(targetSchema).max(1_000);
23
+ /** Discover bounded Node/Electron Inspector targets from one loopback endpoint. */
24
+ export const discoverV8Inspector = async (endpoint, operation, signal) => {
25
+ const [versionInput, targetsInput] = await Promise.all([
26
+ readBoundedCdpJson(new URL("/json/version", endpoint), MAX_VERSION_BYTES, operation, signal),
27
+ readBoundedCdpJson(new URL("/json/list", endpoint), MAX_TARGET_LIST_BYTES, operation, signal),
28
+ ]);
29
+ const version = parseCdpEndpointValue(versionSchema, versionInput, operation);
30
+ const targets = parseCdpEndpointValue(targetsSchema, targetsInput, operation);
31
+ return {
32
+ runtime: {
33
+ product: version.Browser,
34
+ protocol_version: version["Protocol-Version"],
35
+ v8_version: version["V8-Version"] ?? null,
36
+ },
37
+ targets: targets.map((target) => ({
38
+ id: target.id,
39
+ type: target.type,
40
+ url: target.url,
41
+ attached: target.attached,
42
+ webSocketUrl: validatedInspectorWebSocket(endpoint, target.id, target.webSocketDebuggerUrl, operation),
43
+ })),
44
+ };
45
+ };
46
+ const validatedInspectorWebSocket = (endpoint, targetId, reported, operation) => {
47
+ let socket;
48
+ try {
49
+ socket = new URL(reported);
50
+ }
51
+ catch (cause) {
52
+ throw new BrowserObservationError(operation, "invalid_endpoint_response", {
53
+ cause,
54
+ });
55
+ }
56
+ const trusted = new URL(endpoint);
57
+ const allowedPaths = new Set([
58
+ `/${targetId}`,
59
+ `/devtools/node/${targetId}`,
60
+ `/devtools/page/${targetId}`,
61
+ ]);
62
+ if (socket.protocol !== "ws:" ||
63
+ socket.port !== trusted.port ||
64
+ socket.username !== "" ||
65
+ socket.password !== "" ||
66
+ socket.search !== "" ||
67
+ socket.hash !== "" ||
68
+ !allowedPaths.has(socket.pathname))
69
+ throw new BrowserObservationError(operation, "invalid_endpoint_response");
70
+ socket.hostname = trusted.hostname;
71
+ return socket.href;
72
+ };