rea-agents 2.3.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 (209) 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/ArtifactExtraction.js +5 -1
  7. package/dist/application/ArtifactGraphConstruction.js +3 -15
  8. package/dist/application/ArtifactInventory/classify.js +5 -1
  9. package/dist/application/ArtifactInventory/reader.js +2 -0
  10. package/dist/application/BinarySession.js +15 -4
  11. package/dist/application/BinarySessionStatus.js +32 -1
  12. package/dist/application/BrowserEvidence.js +14 -0
  13. package/dist/application/BrowserScenarioCapturePort.js +1 -0
  14. package/dist/application/BrowserScenarioCaptureService.js +53 -0
  15. package/dist/application/BrowserScenarioEvidence.js +40 -0
  16. package/dist/application/CallPathTracing.js +160 -0
  17. package/dist/application/CapabilityInventory.js +79 -15
  18. package/dist/application/CompositeProvider.js +10 -1
  19. package/dist/application/DirectAnalysis.js +7 -2
  20. package/dist/application/EnhancedLiteralTracing.js +118 -0
  21. package/dist/application/EnhancedTools.js +35 -121
  22. package/dist/application/JavaScriptApplicationWorkflowEvidence.js +13 -0
  23. package/dist/application/JavaScriptApplicationWorkflowService.js +27 -1
  24. package/dist/application/JavaScriptRuntimeObservationEvidence.js +34 -0
  25. package/dist/application/JavaScriptRuntimeObservationPort.js +1 -0
  26. package/dist/application/JavaScriptRuntimeObservationService.js +46 -0
  27. package/dist/application/JavaScriptSemanticGraphAsyncProjection.js +147 -0
  28. package/dist/application/JavaScriptSemanticGraphBuilder.js +34 -6
  29. package/dist/application/JavaScriptSemanticGraphChildProcessProjection.js +156 -0
  30. package/dist/application/JavaScriptSemanticGraphConstruction.js +2 -2
  31. package/dist/application/JavaScriptSemanticGraphDataProjection.js +203 -0
  32. package/dist/application/JavaScriptSemanticGraphFingerprintProjection.js +25 -0
  33. package/dist/application/JavaScriptSemanticGraphFlowProjection.js +129 -8
  34. package/dist/application/JavaScriptSemanticGraphObjectProjection.js +81 -0
  35. package/dist/application/JavaScriptSemanticGraphProjection.js +14 -7
  36. package/dist/application/JavaScriptSemanticGraphResourceProjection.js +59 -0
  37. package/dist/application/JavaScriptSemanticGraphValueProjection.js +77 -0
  38. package/dist/application/ProcessCaptureEnvironment.js +26 -0
  39. package/dist/application/ProcessCaptureLifecycle.js +1 -0
  40. package/dist/application/ProcessCheckpoints.js +5 -0
  41. package/dist/application/ProcessHarness.js +45 -34
  42. package/dist/application/ProcessNormalization.js +6 -0
  43. package/dist/application/ProcessReactiveCoordinator.js +10 -3
  44. package/dist/application/ProcessReactiveEffects.js +15 -1
  45. package/dist/application/ProcessReactiveHarness.js +100 -0
  46. package/dist/application/ProcessReactiveObservations.js +101 -16
  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/artifacts/ArtifactProvider.js +87 -22
  58. package/dist/browser/BrowserScenarioSecrets.js +53 -0
  59. package/dist/browser/BrowserScenarioSessionPort.js +1 -0
  60. package/dist/browser/CdpBrowserProvider.js +2 -2
  61. package/dist/browser/CdpCaptureDocuments.js +57 -0
  62. package/dist/browser/CdpCaptureStorage.js +71 -29
  63. package/dist/browser/CdpCaptureStorageFingerprints.js +328 -0
  64. package/dist/browser/CdpEndpoint.js +8 -6
  65. package/dist/browser/CdpPageCapture.js +2 -0
  66. package/dist/browser/JavaScriptRuntimeScope.js +38 -0
  67. package/dist/browser/PlaywrightBrowserScenarioProvider.js +253 -0
  68. package/dist/browser/PlaywrightExecutionBoundary.js +42 -0
  69. package/dist/browser/PlaywrightScenarioActions.js +62 -0
  70. package/dist/browser/PlaywrightScenarioArtifacts.js +238 -0
  71. package/dist/browser/PlaywrightScenarioBrowser.js +144 -0
  72. package/dist/browser/PlaywrightScenarioEvents.js +301 -0
  73. package/dist/browser/PlaywrightScenarioSession.js +300 -0
  74. package/dist/browser/V8InspectorCaptureProjection.js +110 -0
  75. package/dist/browser/V8InspectorEndpoint.js +72 -0
  76. package/dist/browser/V8InspectorProvider.js +267 -0
  77. package/dist/catalogIdentity.js +8 -0
  78. package/dist/cli/artifactCommands.js +39 -0
  79. package/dist/cli/coreAnalysisCommands.js +37 -0
  80. package/dist/cli.js +4 -0
  81. package/dist/cliApplicationCommands.js +30 -2
  82. package/dist/cliBrowserAdvancedCommands.js +17 -5
  83. package/dist/cliBrowserCommands.js +1 -0
  84. package/dist/cliBrowserScenarioCommands.js +43 -0
  85. package/dist/cliCommandNames.js +8 -0
  86. package/dist/cliJavaScriptRuntimeCommands.js +164 -0
  87. package/dist/cliObservationOptions.js +4 -0
  88. package/dist/cliPolicyCommands.js +1 -0
  89. package/dist/config/environment.js +12 -0
  90. package/dist/config/parseConfig.js +88 -17
  91. package/dist/contracts/analysisSearchContract.js +14 -0
  92. package/dist/contracts/applicationToolContracts.js +86 -2
  93. package/dist/contracts/applicationWorkflowInputContracts.js +18 -0
  94. package/dist/contracts/artifactToolContracts.js +22 -0
  95. package/dist/contracts/browserProviderToolContracts.js +7 -0
  96. package/dist/contracts/browserScenarioToolContracts.js +81 -0
  97. package/dist/contracts/browserToolContracts.js +88 -98
  98. package/dist/contracts/enhancedInputs.js +25 -6
  99. package/dist/contracts/functionInstructionContract.js +8 -0
  100. package/dist/contracts/hopperMemoryContracts.js +22 -0
  101. package/dist/contracts/javascriptApplicationWorkflowExamples.js +42 -0
  102. package/dist/contracts/javascriptRuntimeObservationToolContracts.js +58 -0
  103. package/dist/contracts/reconstructionReadinessExample.js +436 -0
  104. package/dist/contracts/toolContractExamples.js +5 -0
  105. package/dist/contracts/toolContractTypes.js +1 -0
  106. package/dist/contracts/toolContracts.js +13 -21
  107. package/dist/contracts/toolEffects.js +21 -0
  108. package/dist/contracts/toolOutputSchemaGroups.js +70 -20
  109. package/dist/contracts/toolOutputSchemaPrimitives.js +40 -0
  110. package/dist/domain/analysisErrorProjection.js +8 -0
  111. package/dist/domain/analysisSnapshot.js +2 -0
  112. package/dist/domain/artifactGraph.js +2 -0
  113. package/dist/domain/artifactInspection.js +347 -0
  114. package/dist/domain/binaryTarget.js +3 -6
  115. package/dist/domain/browserCaptureComparison.js +82 -0
  116. package/dist/domain/browserObservation.js +8 -1
  117. package/dist/domain/browserObservationErrors.js +1 -0
  118. package/dist/domain/browserObservationSchemas.js +26 -0
  119. package/dist/domain/browserScenario.js +165 -0
  120. package/dist/domain/browserScenarioCapture.js +57 -0
  121. package/dist/domain/browserScenarioCaptureValues.js +204 -0
  122. package/dist/domain/browserScenarioDiff.js +324 -0
  123. package/dist/domain/browserScenarioDiffValues.js +157 -0
  124. package/dist/domain/browserScenarioNormalization.js +45 -0
  125. package/dist/domain/browserScenarioValues.js +372 -0
  126. package/dist/domain/evidence.js +2 -0
  127. package/dist/domain/hopperValues.js +10 -0
  128. package/dist/domain/javascriptRuntimeObservation.js +170 -0
  129. package/dist/domain/javascriptRuntimeReconciliationParsing.js +120 -2
  130. package/dist/domain/javascriptRuntimeReconciliationResult.js +3 -3
  131. package/dist/domain/javascriptRuntimeReconciliationRuntime.js +2 -2
  132. package/dist/domain/javascriptRuntimeReconciliationSchemas.js +1 -1
  133. package/dist/domain/javascriptSemanticAnalysis.js +5 -4
  134. package/dist/domain/javascriptSemanticAsyncEffects.js +253 -0
  135. package/dist/domain/javascriptSemanticChildProcesses.js +237 -0
  136. package/dist/domain/javascriptSemanticDataEffectHelpers.js +125 -0
  137. package/dist/domain/javascriptSemanticDataEffects.js +284 -0
  138. package/dist/domain/javascriptSemanticDerivedAnalysis.js +41 -0
  139. package/dist/domain/javascriptSemanticFingerprints.js +174 -0
  140. package/dist/domain/javascriptSemanticIr.js +21 -1
  141. package/dist/domain/javascriptSemanticObjects.js +115 -0
  142. package/dist/domain/javascriptSemanticPromises.js +290 -0
  143. package/dist/domain/javascriptSemanticResources.js +136 -0
  144. package/dist/domain/javascriptStaticAnalysis.js +4 -3
  145. package/dist/domain/javascriptStaticAnalysisBundler.js +38 -0
  146. package/dist/domain/javascriptStaticAnalysisCalls.js +23 -1
  147. package/dist/domain/javascriptStaticAnalysisHelpers.js +8 -0
  148. package/dist/domain/processCapture.js +71 -1
  149. package/dist/domain/processCaptureReactiveValidation.js +175 -0
  150. package/dist/domain/processCaptureValidation.js +2 -0
  151. package/dist/domain/processObservation.js +15 -6
  152. package/dist/domain/processReactiveRuntime.js +21 -10
  153. package/dist/domain/processReactiveScenario.js +1 -0
  154. package/dist/domain/processReactiveTransition.js +17 -10
  155. package/dist/domain/processScenario.js +21 -0
  156. package/dist/domain/providerCleanupError.js +11 -0
  157. package/dist/domain/reconstructionObligationLedgerSchemas.js +278 -0
  158. package/dist/domain/reconstructionReadiness.js +121 -0
  159. package/dist/domain/reconstructionReadinessFindingHelpers.js +17 -0
  160. package/dist/domain/reconstructionReadinessIntegrityFindings.js +118 -0
  161. package/dist/domain/reconstructionReadinessJourneyFindings.js +193 -0
  162. package/dist/domain/reconstructionReadinessSchemas.js +240 -0
  163. package/dist/domain/sourceToBundleComparison.js +335 -0
  164. package/dist/domain/sourceToBundleComparisonSchemas.js +133 -0
  165. package/dist/domain/sourceToBundleSignals.js +202 -0
  166. package/dist/domain/webCaptureDiff.js +141 -27
  167. package/dist/domain/webScreenshot.js +1 -1
  168. package/dist/domain/zipPackageFormat.js +20 -0
  169. package/dist/generatedPackageMetadata.js +1 -1
  170. package/dist/ghidra/GhidraDefaults.js +1 -1
  171. package/dist/ghidra/GhidraFunctionValues.js +13 -1
  172. package/dist/ghidra/GhidraProviderCapabilities.js +7 -0
  173. package/dist/hopper/HopperCleanup.js +158 -0
  174. package/dist/hopper/HopperClient.js +69 -99
  175. package/dist/hopper/HopperProvider.js +20 -1
  176. package/dist/hopper/HopperRequestQueue.js +187 -0
  177. package/dist/hopper/HopperResponseStream.js +46 -0
  178. package/dist/hopper/protocol.js +1 -0
  179. package/dist/main/transport.js +24 -24
  180. package/dist/main.js +6 -0
  181. package/dist/mcpDoctor.js +25 -2
  182. package/dist/native/NativeMacOSProvider.js +1 -0
  183. package/dist/process/ProcessCommandIdentity.js +19 -0
  184. package/dist/process/ProcessOwnership.js +11 -8
  185. package/dist/server/DynamicToolAvailability.js +70 -0
  186. package/dist/server/createServer.js +46 -1
  187. package/dist/server/lifecycleProgress.js +10 -0
  188. package/dist/server/permissionFailure.js +12 -0
  189. package/dist/server/registerApplicationTools/compareSourceToBundle.js +35 -0
  190. package/dist/server/registerApplicationTools/helpers.js +11 -7
  191. package/dist/server/registerApplicationTools/obligationLedger.js +27 -0
  192. package/dist/server/registerApplicationTools/readiness.js +55 -0
  193. package/dist/server/registerApplicationTools.js +6 -0
  194. package/dist/server/registerArtifactTools.js +9 -1
  195. package/dist/server/registerBrowserScenarioTool.js +25 -0
  196. package/dist/server/registerBrowserTools.js +2 -2
  197. package/dist/server/registerCloseLifecycleTool.js +57 -0
  198. package/dist/server/registerEnhancedTools.js +16 -5
  199. package/dist/server/registerEvidenceResources.js +4 -0
  200. package/dist/server/registerEvidenceTools.js +9 -2
  201. package/dist/server/registerJavaScriptRuntimeObservationTools.js +37 -0
  202. package/dist/server/registerReconstructionObligationLedgerResource.js +39 -0
  203. package/dist/server/registerReconstructionReadinessResource.js +39 -0
  204. package/dist/server/registerSessionStatusTool.js +11 -8
  205. package/dist/server/registerSessionTools.js +11 -57
  206. package/dist/server/sessionAvailabilityPolicy.js +10 -8
  207. package/package.json +12 -6
  208. package/skills/reverse-engineer-anything/SKILL.md +4 -3
  209. package/skills/reverse-engineer-anything/references/native-and-artifacts.md +2 -2
@@ -0,0 +1,328 @@
1
+ import { createHash } from "node:crypto";
2
+ import canonicalize from "canonicalize";
3
+ import { CdpConnection } from "./CdpConnection.js";
4
+ import { optionalCdpCommand } from "./CdpOptionalCommand.js";
5
+ import { recordValue, recordsValue, stringValue } from "./CdpCaptureValues.js";
6
+ const MAX_CACHE_BODY_BYTES = 64 * 1_024;
7
+ /** Capture stable hashes of approved storage content without returning values. */
8
+ export const captureStorageFingerprints = async ({ context, origin, local, session, indexedDbNames, caches, limitations, }) => {
9
+ const state = {
10
+ items: [],
11
+ maximum: context.input.limits.max_storage_keys,
12
+ complete: local.complete && session.complete,
13
+ truncated: false,
14
+ };
15
+ const run = { context, limitations, state };
16
+ addStorageItems(state, "local_storage", local.items);
17
+ addStorageItems(state, "session_storage", session.items);
18
+ await addCookies(run, origin);
19
+ await addIndexedDb(run, origin, indexedDbNames);
20
+ await addCaches(run, caches);
21
+ state.items.sort((left, right) => compareCodePoints(`${left.scope}:${left.identity_sha256}`, `${right.scope}:${right.identity_sha256}`));
22
+ return {
23
+ items: state.items,
24
+ complete: state.complete &&
25
+ !state.truncated &&
26
+ state.items.every(({ complete }) => complete),
27
+ truncated: state.truncated,
28
+ };
29
+ };
30
+ const addStorageItems = (state, scope, items) => {
31
+ for (const item of items)
32
+ addFingerprint(state, {
33
+ scope,
34
+ identity: item.key,
35
+ value: item.value,
36
+ complete: true,
37
+ });
38
+ };
39
+ const addCookies = async (run, origin) => {
40
+ const raw = await optionalCdpCommand(run.context, "Network.getCookies", { urls: [origin] }, run.limitations);
41
+ if (raw === undefined) {
42
+ run.state.complete = false;
43
+ return;
44
+ }
45
+ const cookieValues = recordValue(raw)?.cookies;
46
+ if (!Array.isArray(cookieValues)) {
47
+ run.state.complete = false;
48
+ return;
49
+ }
50
+ const cookies = recordsValue(cookieValues);
51
+ if (cookies.length !== cookieValues.length)
52
+ run.state.complete = false;
53
+ for (const cookie of cookies) {
54
+ const name = stringValue(cookie.name);
55
+ const value = stringValue(cookie.value);
56
+ const domain = stringValue(cookie.domain);
57
+ const path = stringValue(cookie.path);
58
+ if (name === undefined ||
59
+ value === undefined ||
60
+ domain === undefined ||
61
+ path === undefined) {
62
+ run.state.complete = false;
63
+ continue;
64
+ }
65
+ addFingerprint(run.state, {
66
+ scope: "cookie",
67
+ identity: {
68
+ domain,
69
+ path,
70
+ name,
71
+ partition_key: cookie.partitionKey ?? null,
72
+ },
73
+ value,
74
+ complete: true,
75
+ });
76
+ }
77
+ };
78
+ const addIndexedDb = async (run, origin, names) => {
79
+ for (const databaseName of names) {
80
+ if (run.state.items.length >= run.state.maximum) {
81
+ run.state.truncated = true;
82
+ return;
83
+ }
84
+ const raw = await optionalCdpCommand(run.context, "IndexedDB.requestDatabase", { securityOrigin: origin, databaseName }, run.limitations);
85
+ const database = recordValue(recordValue(raw)?.databaseWithObjectStores);
86
+ if (raw === undefined || database === undefined) {
87
+ run.state.complete = false;
88
+ continue;
89
+ }
90
+ if (stringValue(database.name) !== databaseName) {
91
+ run.state.complete = false;
92
+ continue;
93
+ }
94
+ if (!Array.isArray(database.objectStores)) {
95
+ run.state.complete = false;
96
+ continue;
97
+ }
98
+ const stores = recordsValue(database.objectStores);
99
+ if (stores.length !== database.objectStores.length)
100
+ run.state.complete = false;
101
+ addFingerprint(run.state, {
102
+ scope: "indexed_db_schema",
103
+ identity: databaseName,
104
+ value: stores.map(indexedDbStoreSchema),
105
+ complete: indexedDbSchemaComplete(stores),
106
+ });
107
+ for (const store of stores) {
108
+ const objectStoreName = stringValue(store.name);
109
+ if (objectStoreName === undefined) {
110
+ run.state.complete = false;
111
+ continue;
112
+ }
113
+ await addIndexedDbRecords(run, {
114
+ origin,
115
+ databaseName,
116
+ objectStoreName,
117
+ });
118
+ }
119
+ }
120
+ };
121
+ const indexedDbStoreSchema = (store) => ({
122
+ name: stringValue(store.name) ?? null,
123
+ key_path: stableValue(store.keyPath),
124
+ auto_increment: store.autoIncrement === true,
125
+ indexes: recordsValue(store.indexes).map((index) => ({
126
+ name: stringValue(index.name) ?? null,
127
+ key_path: stableValue(index.keyPath),
128
+ unique: index.unique === true,
129
+ multi_entry: index.multiEntry === true,
130
+ })),
131
+ });
132
+ const indexedDbSchemaComplete = (stores) => stores.every((store) => stringValue(store.name) !== undefined &&
133
+ stableValueComplete(store.keyPath) &&
134
+ typeof store.autoIncrement === "boolean" &&
135
+ Array.isArray(store.indexes) &&
136
+ recordsValue(store.indexes).length === store.indexes.length &&
137
+ recordsValue(store.indexes).every((index) => stringValue(index.name) !== undefined &&
138
+ stableValueComplete(index.keyPath) &&
139
+ typeof index.unique === "boolean" &&
140
+ typeof index.multiEntry === "boolean"));
141
+ const addIndexedDbRecords = async (run, input) => {
142
+ const remaining = run.state.maximum - run.state.items.length;
143
+ if (remaining <= 0) {
144
+ run.state.truncated = true;
145
+ return;
146
+ }
147
+ const raw = await optionalCdpCommand(run.context, "IndexedDB.requestData", {
148
+ securityOrigin: input.origin,
149
+ databaseName: input.databaseName,
150
+ objectStoreName: input.objectStoreName,
151
+ indexName: "",
152
+ skipCount: 0,
153
+ pageSize: remaining,
154
+ }, run.limitations);
155
+ const result = recordValue(raw);
156
+ if (raw === undefined || result === undefined) {
157
+ run.state.complete = false;
158
+ return;
159
+ }
160
+ if (!Array.isArray(result.objectStoreDataEntries) ||
161
+ typeof result.hasMore !== "boolean") {
162
+ run.state.complete = false;
163
+ return;
164
+ }
165
+ const entries = recordsValue(result.objectStoreDataEntries);
166
+ if (entries.length !== result.objectStoreDataEntries.length)
167
+ run.state.complete = false;
168
+ for (const entry of entries) {
169
+ const key = stableValue(entry.key);
170
+ const primaryKey = stableValue(entry.primaryKey);
171
+ const value = stableValue(entry.value);
172
+ addFingerprint(run.state, {
173
+ scope: "indexed_db_record",
174
+ identity: {
175
+ database: input.databaseName,
176
+ store: input.objectStoreName,
177
+ key,
178
+ primaryKey,
179
+ },
180
+ value,
181
+ complete: remoteObjectComplete(entry.value),
182
+ });
183
+ }
184
+ if (result.hasMore === true || entries.length > remaining)
185
+ run.state.truncated = true;
186
+ };
187
+ const addCaches = async (run, caches) => {
188
+ for (const cache of caches) {
189
+ const remaining = run.state.maximum - run.state.items.length;
190
+ if (remaining <= 0) {
191
+ run.state.truncated = true;
192
+ return;
193
+ }
194
+ const raw = await optionalCdpCommand(run.context, "CacheStorage.requestEntries", { cacheId: cache.id, skipCount: 0, pageSize: remaining }, run.limitations);
195
+ const result = recordValue(raw);
196
+ if (raw === undefined || result === undefined) {
197
+ run.state.complete = false;
198
+ continue;
199
+ }
200
+ if (!Array.isArray(result.cacheDataEntries) ||
201
+ typeof result.returnCount !== "number") {
202
+ run.state.complete = false;
203
+ continue;
204
+ }
205
+ const entries = recordsValue(result.cacheDataEntries);
206
+ if (entries.length !== result.cacheDataEntries.length)
207
+ run.state.complete = false;
208
+ for (const entry of entries)
209
+ await addCacheEntry(run, cache, entry);
210
+ if (result.returnCount > entries.length)
211
+ run.state.truncated = true;
212
+ }
213
+ };
214
+ const addCacheEntry = async (run, cache, entry) => {
215
+ const requestURL = stringValue(entry.requestURL);
216
+ const requestMethod = stringValue(entry.requestMethod);
217
+ if (requestURL === undefined || requestMethod === undefined) {
218
+ run.state.complete = false;
219
+ return;
220
+ }
221
+ if (!Array.isArray(entry.requestHeaders)) {
222
+ run.state.complete = false;
223
+ return;
224
+ }
225
+ const headerRecords = recordsValue(entry.requestHeaders);
226
+ const requestHeaders = headerRecords.flatMap((header) => {
227
+ const name = stringValue(header.name);
228
+ const value = stringValue(header.value);
229
+ return name === undefined || value === undefined ? [] : [{ name, value }];
230
+ });
231
+ if (headerRecords.length !== entry.requestHeaders.length ||
232
+ requestHeaders.length !== headerRecords.length) {
233
+ run.state.complete = false;
234
+ return;
235
+ }
236
+ const raw = await optionalCdpCommand(run.context, "CacheStorage.requestCachedResponse", { cacheId: cache.id, requestURL, requestHeaders }, run.limitations);
237
+ const body = stringValue(recordValue(recordValue(raw)?.response)?.body);
238
+ const decoded = body === undefined ? null : decodeBase64(body);
239
+ const complete = decoded !== null && decoded.byteLength <= MAX_CACHE_BODY_BYTES;
240
+ if (!complete)
241
+ run.state.complete = false;
242
+ addFingerprint(run.state, {
243
+ scope: "cache_entry",
244
+ identity: { cache: cache.name, requestURL, requestMethod, requestHeaders },
245
+ value: decoded ?? undefined,
246
+ complete,
247
+ });
248
+ };
249
+ const addFingerprint = (state, input) => {
250
+ if (state.items.length >= state.maximum) {
251
+ state.truncated = true;
252
+ return;
253
+ }
254
+ state.items.push({
255
+ scope: input.scope,
256
+ identity_sha256: digest(input.identity),
257
+ value_sha256: input.value === undefined ? null : digest(input.value),
258
+ complete: input.complete &&
259
+ stableValueComplete(input.identity) &&
260
+ stableValueComplete(input.value),
261
+ });
262
+ };
263
+ const digest = (value) => createHash("sha256").update(encodedValue(value)).digest("hex");
264
+ const encodedValue = (value) => {
265
+ if (value instanceof Uint8Array)
266
+ return value;
267
+ const encoded = canonicalize(stableValue(value));
268
+ if (encoded === undefined)
269
+ throw new TypeError("Expected canonical JSON");
270
+ return encoded;
271
+ };
272
+ const stableValue = (value, depth = 0) => {
273
+ if (value === null ||
274
+ typeof value === "string" ||
275
+ typeof value === "number" ||
276
+ typeof value === "boolean")
277
+ return value;
278
+ if (depth >= 8)
279
+ return "<depth-limit>";
280
+ if (Array.isArray(value))
281
+ return value.slice(0, 64).map((item) => stableValue(item, depth + 1));
282
+ const record = recordValue(value);
283
+ if (record === undefined)
284
+ return String(value);
285
+ return Object.fromEntries(Object.entries(record)
286
+ .filter(([key]) => key !== "objectId" && key !== "customPreview")
287
+ .sort(([left], [right]) => compareCodePoints(left, right))
288
+ .slice(0, 64)
289
+ .map(([key, item]) => [key, stableValue(item, depth + 1)]));
290
+ };
291
+ const stableValueComplete = (value, depth = 0) => {
292
+ if (value instanceof Uint8Array)
293
+ return true;
294
+ if (value === null ||
295
+ value === undefined ||
296
+ typeof value === "string" ||
297
+ typeof value === "number" ||
298
+ typeof value === "boolean")
299
+ return true;
300
+ if (depth >= 8)
301
+ return false;
302
+ if (Array.isArray(value))
303
+ return (value.length <= 64 &&
304
+ value.every((item) => stableValueComplete(item, depth + 1)));
305
+ const record = recordValue(value);
306
+ if (record === undefined)
307
+ return false;
308
+ const entries = Object.entries(record).filter(([key]) => key !== "objectId" && key !== "customPreview");
309
+ return (entries.length <= 64 &&
310
+ entries.every(([, item]) => stableValueComplete(item, depth + 1)));
311
+ };
312
+ const remoteObjectComplete = (value) => {
313
+ const record = recordValue(value);
314
+ if (record === undefined)
315
+ return true;
316
+ return (record.objectId === undefined &&
317
+ record.preview === undefined &&
318
+ record.customPreview === undefined &&
319
+ (record.type !== "object" || record.value !== undefined) &&
320
+ stableValueComplete(record));
321
+ };
322
+ const decodeBase64 = (value) => {
323
+ if (value.length % 4 !== 0 ||
324
+ !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u.test(value))
325
+ return null;
326
+ return Buffer.from(value, "base64");
327
+ };
328
+ const compareCodePoints = (left, right) => left < right ? -1 : left > right ? 1 : 0;
@@ -24,10 +24,10 @@ const endpointTargetSchema = z.object({
24
24
  const endpointTargetsSchema = z.array(endpointTargetSchema).max(1_000);
25
25
  /** Read bounded CDP discovery endpoints without following redirects. */
26
26
  export const discoverCdpEndpoint = async (endpoint, operation, signal) => {
27
- const versionInput = await readJson(new URL("/json/version", endpoint), MAX_VERSION_BYTES, operation, signal);
28
- const targetsInput = await readJson(new URL("/json/list", endpoint), MAX_TARGET_LIST_BYTES, operation, signal);
29
- const version = parseEndpointValue(endpointVersionSchema, versionInput, operation);
30
- const targets = parseEndpointValue(endpointTargetsSchema, targetsInput, operation);
27
+ const versionInput = await readBoundedCdpJson(new URL("/json/version", endpoint), MAX_VERSION_BYTES, operation, signal);
28
+ const targetsInput = await readBoundedCdpJson(new URL("/json/list", endpoint), MAX_TARGET_LIST_BYTES, operation, signal);
29
+ const version = parseCdpEndpointValue(endpointVersionSchema, versionInput, operation);
30
+ const targets = parseCdpEndpointValue(endpointTargetsSchema, targetsInput, operation);
31
31
  return {
32
32
  webSocket: safeCdpWebSocketEndpoint(endpoint, version.webSocketDebuggerUrl, operation),
33
33
  version: {
@@ -110,7 +110,8 @@ const availableCdpTargetWebSocket = (discovery, target) => {
110
110
  ? discovery.webSocket
111
111
  : undefined;
112
112
  };
113
- const parseEndpointValue = (schema, input, operation) => {
113
+ /** Parse one CDP discovery value through its exact boundary schema. */
114
+ export const parseCdpEndpointValue = (schema, input, operation) => {
114
115
  const parsed = schema.safeParse(input);
115
116
  if (!parsed.success)
116
117
  throw new BrowserObservationError(operation, "invalid_endpoint_response", {
@@ -173,7 +174,8 @@ const targetWebSocket = (endpoint, target, operation) => {
173
174
  return {};
174
175
  }
175
176
  };
176
- const readJson = async (url, maximumBytes, operation, signal) => await new Promise((resolve, reject) => {
177
+ /** Read one bounded loopback CDP discovery document without redirects. */
178
+ export const readBoundedCdpJson = async (url, maximumBytes, operation, signal) => await new Promise((resolve, reject) => {
177
179
  if (signal?.aborted === true) {
178
180
  reject(new AnalysisCancelledError(operation));
179
181
  return;
@@ -226,6 +226,8 @@ const accessibilityForFrames = async (state, frames) => {
226
226
  events.completeness.exclude("accessibility", "not_approved", capture.textCapture.excluded_fields);
227
227
  if (capture.textCapture.status === "truncated")
228
228
  events.completeness.truncate("accessibility");
229
+ if (capture.treeIncomplete)
230
+ events.completeness.truncate("accessibility");
229
231
  return capture;
230
232
  };
231
233
  const publicResource = ({ rawUrl: _rawUrl, ...resource }) => resource;
@@ -0,0 +1,38 @@
1
+ import { browserOriginSchema, sanitizeBrowserUrl, } from "../domain/browserObservation.js";
2
+ import { authorizedElectronFile, canonicalElectronRoots, } from "./ElectronFileScope.js";
3
+ /** Canonicalize exact filesystem scopes before Inspector attachment. */
4
+ export const canonicalRuntimeRoots = canonicalElectronRoots;
5
+ /** Authorize one protocol URL without retaining an out-of-scope value. */
6
+ export const authorizeRuntimeLocation = async (value, roots, origins) => {
7
+ if (value.startsWith("node:") && value.length <= 4_096)
8
+ return {
9
+ allowed: true,
10
+ location: { kind: "builtin", specifier: value },
11
+ };
12
+ if (value.startsWith("file:")) {
13
+ const filePath = await authorizedElectronFile(value, roots);
14
+ return filePath === undefined
15
+ ? { allowed: false, reason: "outside_file_roots" }
16
+ : { allowed: true, location: { kind: "file", file_path: filePath } };
17
+ }
18
+ let url;
19
+ try {
20
+ url = new URL(value);
21
+ }
22
+ catch {
23
+ return { allowed: false, reason: "unsupported_location" };
24
+ }
25
+ if (!["http:", "https:"].includes(url.protocol))
26
+ return { allowed: false, reason: "unsupported_location" };
27
+ const parsedOrigin = browserOriginSchema.safeParse(url.origin);
28
+ if (!parsedOrigin.success || !origins.includes(parsedOrigin.data))
29
+ return { allowed: false, reason: "outside_origins" };
30
+ return {
31
+ allowed: true,
32
+ location: {
33
+ kind: "url",
34
+ origin: parsedOrigin.data,
35
+ sanitized_url: sanitizeBrowserUrl(value).url,
36
+ },
37
+ };
38
+ };
@@ -0,0 +1,253 @@
1
+ import { sanitizeBrowserUrl } from "../domain/browserObservation.js";
2
+ import { browserScenarioCaptureSchema, browserScenarioCompletenessSchema, browserScenarioStepSchema, browserStepArtifactsSchema, } from "../domain/browserScenarioCapture.js";
3
+ import { AnalysisError, BrowserObservationError, ProviderAdapterError, } from "../domain/errors.js";
4
+ import { err, ok } from "../domain/result.js";
5
+ import { BrowserScenarioCaptureBudget } from "./PlaywrightScenarioArtifacts.js";
6
+ import { PlaywrightScenarioSessionFactory } from "./PlaywrightScenarioSession.js";
7
+ const OPERATION = "capture_browser_scenario";
8
+ export const PLAYWRIGHT_BROWSER_SCENARIO_PROVIDER_IDENTITY = Object.freeze({
9
+ id: "rea-playwright-browser-scenario",
10
+ name: "REA Playwright browser scenario capture provider",
11
+ version: "1",
12
+ });
13
+ const requestedForStep = (scenario, stepIndex) => new Set([
14
+ ...scenario.capture.after_each_step,
15
+ ...(stepIndex === scenario.actions.length ? scenario.capture.at_end : []),
16
+ ]);
17
+ const unavailableArtifacts = (requested, reason) => {
18
+ const state = (kind) => requested.has(kind)
19
+ ? { state: "missing", reason }
20
+ : { state: "not_requested" };
21
+ return browserStepArtifactsSchema.parse({
22
+ screenshot: state("screenshot"),
23
+ dom: state("dom"),
24
+ accessibility: state("accessibility"),
25
+ url: state("url"),
26
+ history: state("history"),
27
+ storage: state("storage"),
28
+ });
29
+ };
30
+ const stepCompleteness = (artifacts, status) => {
31
+ const missing = Object.entries(artifacts)
32
+ .filter(([, state]) => state.state === "missing")
33
+ .map(([section]) => section);
34
+ const truncated = Object.entries(artifacts)
35
+ .filter(([, state]) => state.state === "truncated")
36
+ .map(([section]) => section);
37
+ if (status !== "completed")
38
+ missing.push("action");
39
+ const condition = truncated.length > 0
40
+ ? "truncated"
41
+ : missing.length > 0
42
+ ? "incomplete"
43
+ : "complete";
44
+ return browserScenarioCompletenessSchema.parse({
45
+ status: condition,
46
+ equality_eligible: condition === "complete",
47
+ missing_sections: [...new Set(missing)].sort(),
48
+ truncated_sections: [...new Set(truncated)].sort(),
49
+ });
50
+ };
51
+ const createStep = (input) => browserScenarioStepSchema.parse({
52
+ step_index: input.stepIndex,
53
+ step_id: input.stepId,
54
+ action: input.action,
55
+ status: input.status,
56
+ elapsed_ms: input.elapsedMs,
57
+ before_url: sanitizeBrowserUrl(input.beforeUrl),
58
+ after_url: sanitizeBrowserUrl(input.afterUrl),
59
+ error: input.error,
60
+ event_sequence_start: input.eventStart,
61
+ event_sequence_end: input.eventEnd,
62
+ artifacts: input.artifacts,
63
+ completeness: stepCompleteness(input.artifacts, input.status),
64
+ });
65
+ const initialStep = async (input) => {
66
+ const { session, scenario, budget, elapsedMs, maximumTimeoutMs, signal } = input;
67
+ const artifacts = await session.capture(requestedForStep(scenario, 0), budget, maximumTimeoutMs, signal);
68
+ return createStep({
69
+ stepIndex: 0,
70
+ stepId: "scenario_start",
71
+ action: "goto_start",
72
+ status: "completed",
73
+ elapsedMs,
74
+ beforeUrl: session.initialUrl,
75
+ afterUrl: session.currentUrl(),
76
+ error: null,
77
+ eventStart: 1,
78
+ eventEnd: session.lastEventSequence(),
79
+ artifacts,
80
+ });
81
+ };
82
+ const executeStep = async (input) => {
83
+ const { session, scenario, budget, action, stepIndex, startedAt, priorFailure, signal, } = input;
84
+ const beforeUrl = session.currentUrl();
85
+ const eventStart = session.nextEventSequence();
86
+ const requested = requestedForStep(scenario, stepIndex);
87
+ if (priorFailure)
88
+ return createStep({
89
+ stepIndex,
90
+ stepId: action.step_id,
91
+ action: action.action,
92
+ status: "cancelled",
93
+ elapsedMs: 0,
94
+ beforeUrl,
95
+ afterUrl: beforeUrl,
96
+ error: "not executed after an earlier action failure",
97
+ eventStart,
98
+ eventEnd: session.lastEventSequence(),
99
+ artifacts: unavailableArtifacts(requested, "action was not executed after an earlier failure"),
100
+ });
101
+ const actionStartedAt = Date.now();
102
+ session.setStep(stepIndex);
103
+ const requestCancelled = () => signal?.aborted === true;
104
+ let status = "completed";
105
+ let error = null;
106
+ if (requestCancelled()) {
107
+ status = "cancelled";
108
+ error = "scenario request cancelled";
109
+ }
110
+ else {
111
+ try {
112
+ const elapsed = Date.now() - startedAt;
113
+ const remaining = scenario.limits.max_duration_ms - elapsed;
114
+ if (remaining <= 0)
115
+ throw new Error("Scenario duration limit reached");
116
+ await session.perform(action, remaining, signal);
117
+ }
118
+ catch (cause) {
119
+ status = requestCancelled() ? "cancelled" : "failed";
120
+ error = session.redactError(cause);
121
+ }
122
+ }
123
+ const remaining = scenario.limits.max_duration_ms - (Date.now() - startedAt);
124
+ const artifacts = status === "cancelled" || remaining <= 0
125
+ ? unavailableArtifacts(requested, status === "cancelled"
126
+ ? "scenario request cancelled"
127
+ : "scenario duration limit reached")
128
+ : await session.capture(requested, budget, remaining, signal);
129
+ return createStep({
130
+ stepIndex,
131
+ stepId: action.step_id,
132
+ action: action.action,
133
+ status,
134
+ elapsedMs: Date.now() - actionStartedAt,
135
+ beforeUrl,
136
+ afterUrl: session.currentUrl(),
137
+ error,
138
+ eventStart,
139
+ eventEnd: session.lastEventSequence(),
140
+ artifacts,
141
+ });
142
+ };
143
+ const globalCompleteness = (scenario, session, steps) => {
144
+ const missing = steps.flatMap(({ completeness }) => completeness.missing_sections);
145
+ const truncated = steps.flatMap(({ completeness }) => completeness.truncated_sections);
146
+ truncated.push(...session.eventTruncationSections());
147
+ if (session.mode === "connect")
148
+ missing.push("events");
149
+ const status = truncated.length > 0
150
+ ? "truncated"
151
+ : missing.length > 0
152
+ ? "incomplete"
153
+ : "complete";
154
+ return browserScenarioCompletenessSchema.parse({
155
+ status,
156
+ equality_eligible: status === "complete",
157
+ missing_sections: [...new Set(missing)].sort(),
158
+ truncated_sections: [...new Set(truncated)].sort(),
159
+ });
160
+ };
161
+ const runScenario = async (factory, scenario, options) => {
162
+ if (options.signal?.aborted === true)
163
+ throw new BrowserObservationError(OPERATION, "cancelled");
164
+ const startedAt = Date.now();
165
+ const budget = new BrowserScenarioCaptureBudget(scenario.limits.max_screenshots, scenario.limits.max_total_metadata_bytes);
166
+ const session = await factory.open(scenario, {
167
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
168
+ budget,
169
+ deadlineAt: startedAt + scenario.limits.max_duration_ms,
170
+ });
171
+ const steps = [];
172
+ let cleanup;
173
+ try {
174
+ steps.push(await initialStep({
175
+ session,
176
+ scenario,
177
+ budget,
178
+ elapsedMs: Date.now() - startedAt,
179
+ maximumTimeoutMs: scenario.limits.max_duration_ms - (Date.now() - startedAt),
180
+ signal: options.signal,
181
+ }));
182
+ let failed = false;
183
+ for (const [offset, action] of scenario.actions.entries()) {
184
+ const step = await executeStep({
185
+ session,
186
+ scenario,
187
+ budget,
188
+ action,
189
+ stepIndex: offset + 1,
190
+ startedAt,
191
+ priorFailure: failed,
192
+ signal: options.signal,
193
+ });
194
+ steps.push(step);
195
+ failed ||= step.status !== "completed";
196
+ }
197
+ }
198
+ finally {
199
+ cleanup = await session.close();
200
+ }
201
+ const events = session.events();
202
+ const completeness = globalCompleteness(scenario, session, steps);
203
+ return browserScenarioCaptureSchema.parse({
204
+ schema_version: 1,
205
+ browser: {
206
+ mode: session.mode,
207
+ process_ownership: session.processOwnership,
208
+ cleanup,
209
+ product: session.product,
210
+ version: session.version,
211
+ },
212
+ scenario: {
213
+ start_origin: new URL(scenario.start_url.url).origin,
214
+ allowed_origins: scenario.allowed_origins,
215
+ action_count: scenario.actions.length,
216
+ secret_references: scenario.secrets.map(({ secret_id: id }) => id).sort(),
217
+ },
218
+ duration_ms: Date.now() - startedAt,
219
+ steps,
220
+ events,
221
+ completeness,
222
+ limitations: [
223
+ "Event sequence records provider receipt order; simultaneous browser causality is not inferred.",
224
+ "Response bodies are not retained.",
225
+ "Storage values are hashed only after declared-secret redaction.",
226
+ ...(session.mode === "connect"
227
+ ? [
228
+ "CDP attachment cannot recover pre-attach events or guarantee launch-time context options.",
229
+ ]
230
+ : []),
231
+ ],
232
+ });
233
+ };
234
+ /** Controlled Playwright/CDP scenario driver with exact process ownership. */
235
+ export class PlaywrightBrowserScenarioProvider {
236
+ factory;
237
+ constructor(factory = new PlaywrightScenarioSessionFactory()) {
238
+ this.factory = factory;
239
+ }
240
+ identity() {
241
+ return PLAYWRIGHT_BROWSER_SCENARIO_PROVIDER_IDENTITY;
242
+ }
243
+ async captureScenario(scenario, options = {}) {
244
+ try {
245
+ return ok(await runScenario(this.factory, scenario, options));
246
+ }
247
+ catch (cause) {
248
+ if (cause instanceof AnalysisError)
249
+ return err(cause);
250
+ return err(new ProviderAdapterError(PLAYWRIGHT_BROWSER_SCENARIO_PROVIDER_IDENTITY.id, OPERATION, { cause }));
251
+ }
252
+ }
253
+ }