rea-agents 1.7.0 → 2.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 (156) hide show
  1. package/README.md +38 -31
  2. package/dist/application/CapabilityInventory.js +18 -0
  3. package/dist/application/CrossVersionInvestigation.js +10 -6
  4. package/dist/application/Doctor.js +56 -6
  5. package/dist/application/ElectronBoundaryAnalysis.js +96 -0
  6. package/dist/application/ElectronBoundaryGraph.js +9 -0
  7. package/dist/application/ElectronBoundaryGraphContext.js +62 -0
  8. package/dist/application/ElectronBoundaryGraphIpc.js +242 -0
  9. package/dist/application/ElectronBoundaryGraphNative.js +86 -0
  10. package/dist/application/ElectronBoundaryGraphWindows.js +199 -0
  11. package/dist/application/EnhancedTools.js +38 -13
  12. package/dist/application/InvestigationProviders.js +30 -0
  13. package/dist/application/JavaScriptApplicationEvidence.js +26 -0
  14. package/dist/application/JavaScriptApplicationEvidenceGraph.js +89 -0
  15. package/dist/application/JavaScriptApplicationService.js +74 -0
  16. package/dist/application/JavaScriptApplicationWorkflowEvidence.js +29 -0
  17. package/dist/application/JavaScriptApplicationWorkflowService.js +90 -0
  18. package/dist/application/JavaScriptArtifactAnalysis.js +7 -1
  19. package/dist/application/JavaScriptArtifactGraphBuilder.js +59 -16
  20. package/dist/application/JavaScriptArtifactGraphEvidence.js +24 -14
  21. package/dist/application/JavaScriptArtifactReconstruction.js +3 -0
  22. package/dist/application/JavaScriptArtifactReconstructionInput.js +2 -83
  23. package/dist/application/JavaScriptReplayPlanning.js +201 -0
  24. package/dist/application/JavaScriptReplayService.js +266 -0
  25. package/dist/application/JavaScriptRuntimeReconciliationEvidence.js +25 -0
  26. package/dist/application/JavaScriptRuntimeReconciliationService.js +27 -0
  27. package/dist/application/ManagedApplicationGraphService.js +55 -0
  28. package/dist/application/ManagedMemberComparisonService.js +122 -0
  29. package/dist/application/ManagedNativeVerificationService.js +39 -0
  30. package/dist/application/ManagedReconstructionService.js +58 -0
  31. package/dist/application/ManagedRuntimeCorrelationService.js +103 -0
  32. package/dist/application/PermissionAuthority.js +1 -1
  33. package/dist/application/ProcessSampling.js +15 -11
  34. package/dist/application/ProjectPermissionStore.js +1 -0
  35. package/dist/application/runtime.js +2 -0
  36. package/dist/browser/CdpCaptureEvents.js +49 -0
  37. package/dist/browser/CdpElectronInspection.js +106 -162
  38. package/dist/browser/CdpElectronProvider.js +5 -1
  39. package/dist/browser/CdpElectronScriptEvents.js +69 -0
  40. package/dist/browser/CdpElectronScripts.js +94 -0
  41. package/dist/browser/CdpElectronWorkers.js +56 -0
  42. package/dist/browser/CdpPageCapture.js +8 -4
  43. package/dist/catalogIdentity.js +4 -1
  44. package/dist/cli.js +388 -0
  45. package/dist/cliApplicationCommands.js +62 -0
  46. package/dist/cliCommandNames.js +13 -0
  47. package/dist/cliElectronCommands.js +103 -1
  48. package/dist/cliJsonInput.js +54 -0
  49. package/dist/config.js +83 -0
  50. package/dist/contracts/applicationToolContracts.js +91 -0
  51. package/dist/contracts/artifactToolContracts.js +5 -9
  52. package/dist/contracts/browserToolContracts.js +14 -62
  53. package/dist/contracts/electronToolContracts.js +45 -19
  54. package/dist/contracts/errorSchemas.js +1 -0
  55. package/dist/contracts/investigationExamples.js +0 -6
  56. package/dist/contracts/javascriptApplicationWorkflowExamples.js +46 -0
  57. package/dist/contracts/javascriptRuntimeReconciliationExample.js +208 -0
  58. package/dist/contracts/managedToolContracts.js +153 -0
  59. package/dist/contracts/managedWorkflowExamples.js +346 -0
  60. package/dist/contracts/managedWorkflowToolContracts.js +203 -0
  61. package/dist/contracts/nativeToolContracts.js +2 -6
  62. package/dist/contracts/promptContracts.js +18 -2
  63. package/dist/contracts/toolContractExamples.js +10 -25
  64. package/dist/contracts/toolContracts.js +47 -45
  65. package/dist/contracts/toolEffects.js +160 -0
  66. package/dist/contracts/toolOutputSchemas.js +46 -31
  67. package/dist/doctorRuntime.js +42 -1
  68. package/dist/domain/artifactComparison.js +3 -4
  69. package/dist/domain/browserObservation.js +3 -0
  70. package/dist/domain/bundleComparison.js +4 -4
  71. package/dist/domain/electronObservation.js +13 -0
  72. package/dist/domain/electronStaticAnalysis.js +9 -0
  73. package/dist/domain/electronStaticAnalysisBrowser.js +194 -0
  74. package/dist/domain/electronStaticAnalysisIpc.js +171 -0
  75. package/dist/domain/electronStaticAnalysisNative.js +164 -0
  76. package/dist/domain/electronStaticAnalysisTypes.js +1 -0
  77. package/dist/domain/electronStaticAnalysisValues.js +103 -0
  78. package/dist/domain/errors.js +67 -1
  79. package/dist/domain/functionComparisonSchemas.js +3 -8
  80. package/dist/domain/inputIssueProjection.js +52 -0
  81. package/dist/domain/javascriptApplicationAnalysis.js +155 -0
  82. package/dist/domain/javascriptApplicationChangeGraph.js +231 -0
  83. package/dist/domain/javascriptApplicationEvidenceSchemas.js +18 -0
  84. package/dist/domain/javascriptApplicationGraph.js +2 -1
  85. package/dist/domain/javascriptApplicationGraphSchemas.js +7 -0
  86. package/dist/domain/javascriptApplicationVersionComparison.js +116 -0
  87. package/dist/domain/javascriptApplicationVersionComparisonSchemas.js +139 -0
  88. package/dist/domain/javascriptApplicationVersionItems.js +221 -0
  89. package/dist/domain/javascriptApplicationVersionKeys.js +170 -0
  90. package/dist/domain/javascriptFeatureSeed.js +140 -0
  91. package/dist/domain/javascriptFeatureTrace.js +232 -0
  92. package/dist/domain/javascriptFeatureTraceSchemas.js +136 -0
  93. package/dist/domain/javascriptFeatureTraversal.js +88 -0
  94. package/dist/domain/javascriptNativeHandoff.js +85 -0
  95. package/dist/domain/javascriptReplay.js +361 -0
  96. package/dist/domain/javascriptRuntimeLoadState.js +106 -0
  97. package/dist/domain/javascriptRuntimeReconciliation.js +42 -0
  98. package/dist/domain/javascriptRuntimeReconciliationGraph.js +169 -0
  99. package/dist/domain/javascriptRuntimeReconciliationMatching.js +247 -0
  100. package/dist/domain/javascriptRuntimeReconciliationParsing.js +160 -0
  101. package/dist/domain/javascriptRuntimeReconciliationResult.js +119 -0
  102. package/dist/domain/javascriptRuntimeReconciliationRuntime.js +316 -0
  103. package/dist/domain/javascriptRuntimeReconciliationSchemas.js +245 -0
  104. package/dist/domain/javascriptRuntimeStaticCandidates.js +191 -0
  105. package/dist/domain/javascriptStaticAnalysis.js +31 -43
  106. package/dist/domain/javascriptStaticAnalysisFindings.js +39 -0
  107. package/dist/domain/javascriptStaticAnalysisHelpers.js +10 -0
  108. package/dist/domain/javascriptStaticAnalysisState.js +6 -0
  109. package/dist/domain/managedApplicationGraph.js +556 -0
  110. package/dist/domain/managedArtifact.js +444 -0
  111. package/dist/domain/managedMemberComparison.js +565 -0
  112. package/dist/domain/managedNativeVerification.js +408 -0
  113. package/dist/domain/managedReconstruction.js +183 -0
  114. package/dist/domain/managedRuntimeCorrelation.js +175 -0
  115. package/dist/domain/reconstructionVerificationSchemas.js +1 -3
  116. package/dist/dotnet/ManagedArtifactInspector.js +318 -0
  117. package/dist/dotnet/ManagedMemberInspector.js +887 -0
  118. package/dist/dotnet/ManagedMetadataHeaps.js +131 -0
  119. package/dist/dotnet/ManagedMetadataInventory.js +368 -0
  120. package/dist/dotnet/ManagedMetadataLayout.js +322 -0
  121. package/dist/dotnet/ManagedNativeBoundaryInspector.js +391 -0
  122. package/dist/dotnet/ManagedPeReader.js +157 -0
  123. package/dist/dotnet/ManagedReaderFailure.js +10 -0
  124. package/dist/dotnet/ManagedStaticProvider.js +203 -0
  125. package/dist/generatedPackageMetadata.js +2 -2
  126. package/dist/ghidra/GhidraProvider.js +6 -0
  127. package/dist/main.js +14 -0
  128. package/dist/replay/JavaScriptReplayWorker.js +286 -0
  129. package/dist/replay/LinuxJavaScriptReplayRunner.js +434 -0
  130. package/dist/replay/LinuxRuntimeClosure.js +46 -0
  131. package/dist/replay/LinuxSeccompPolicy.js +40 -0
  132. package/dist/replay/ReplayWorkerProtocol.js +55 -0
  133. package/dist/replay/SystemJavaScriptReplayHost.js +182 -0
  134. package/dist/server/createServer.js +52 -0
  135. package/dist/server/registerApplicationTools.js +115 -0
  136. package/dist/server/registerArtifactComparisonTool.js +16 -9
  137. package/dist/server/registerBrowserTools.js +33 -8
  138. package/dist/server/registerBundleComparisonTool.js +17 -20
  139. package/dist/server/registerElectronTools.js +45 -3
  140. package/dist/server/registerEnhancedTools.js +58 -13
  141. package/dist/server/registerEvidenceTools.js +5 -1
  142. package/dist/server/registerFunctionComparisonTool.js +14 -7
  143. package/dist/server/registerInvestigationTools.js +21 -17
  144. package/dist/server/registerManagedTools.js +6 -0
  145. package/dist/server/registerManagedWorkflowTools.js +277 -0
  146. package/dist/server/registerOfficialTools.js +8 -8
  147. package/dist/server/registerProcessComparisonTool.js +25 -47
  148. package/dist/server/registerSessionStatusTool.js +7 -7
  149. package/dist/server/registerSessionTools.js +51 -86
  150. package/dist/server/sessionEvidence.js +14 -19
  151. package/dist/server/toolInputValidation.js +77 -0
  152. package/dist/server/toolRegistrationOptions.js +79 -2
  153. package/dist/server/toolResult.js +25 -17
  154. package/package.json +12 -4
  155. package/scripts/rea.mjs +1 -1
  156. package/skills/rea-analysis/SKILL.md +59 -3
@@ -1,78 +1,126 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import canonicalize from "canonicalize";
3
3
  import { BrowserObservationError } from "../domain/errors.js";
4
- import { createWebTextArtifact } from "../domain/webContentArtifact.js";
5
4
  import { CdpCaptureCompleteness } from "./CdpCaptureCompleteness.js";
6
- import { boundedText, delayWithCancellation, numberValue, recordValue, recordsValue, requiredRecord, stringValue, } from "./CdpCaptureValues.js";
5
+ import { delayWithCancellation, numberValue, recordValue, recordsValue, requiredRecord, stringValue, } from "./CdpCaptureValues.js";
6
+ import { ingestElectronScriptEvent, } from "./CdpElectronScriptEvents.js";
7
+ import { captureElectronScripts } from "./CdpElectronScripts.js";
8
+ import { captureElectronWorkers } from "./CdpElectronWorkers.js";
7
9
  import { authorizedElectronFile, canonicalElectronRoots, } from "./ElectronFileScope.js";
8
10
  /** Passively inspect one root-confined Electron file page. */
9
11
  export const inspectCdpElectronPage = async (context) => {
10
- const roots = await canonicalElectronRoots(context.input.allowed_file_roots);
11
- const completeness = new CdpCaptureCompleteness();
12
- const scripts = [];
13
- let mainFrameId;
14
- let navigationDuringCapture = false;
12
+ const state = {
13
+ roots: await canonicalElectronRoots(context.input.allowed_file_roots),
14
+ completeness: new CdpCaptureCompleteness(),
15
+ scripts: [],
16
+ executionContextFrames: new Map(),
17
+ limitations: [
18
+ "Only canonical file paths contained by an approved root are retained.",
19
+ "REA does not evaluate renderer JavaScript, invoke Electron APIs, navigate, click, or close the page.",
20
+ "Script contents require separate source-capture approval and remain byte bounded.",
21
+ ],
22
+ mainFrameId: undefined,
23
+ navigationDuringCapture: false,
24
+ };
15
25
  const removeListener = context.connection.onEvent((event) => {
16
26
  if (event.sessionId !== context.sessionId)
17
27
  return;
18
- ingestScript(event, scripts, completeness);
19
- if (mainFrameId !== undefined && navigatedFrameId(event) === mainFrameId)
20
- navigationDuringCapture = true;
28
+ ingestElectronScriptEvent({
29
+ event,
30
+ scripts: state.scripts,
31
+ executionContextFrames: state.executionContextFrames,
32
+ completeness: state.completeness,
33
+ });
34
+ if (state.mainFrameId !== undefined &&
35
+ navigatedFrameId(event) === state.mainFrameId)
36
+ state.navigationDuringCapture = true;
21
37
  });
22
- const startedAt = new Date().toISOString();
23
38
  try {
24
- await report(context.progress, 1, "Authorizing Electron file frames");
25
- await context.connection.send("Page.enable", {}, context.sessionId, context.signal);
26
- const before = await context.connection.send("Page.getFrameTree", {}, context.sessionId, context.signal);
27
- const frames = await captureFrames(before, roots, context.input.limits.max_frames, completeness);
28
- const main = frames[0];
29
- if (main === undefined)
30
- throw new BrowserObservationError("inspect_web_page", "target_not_allowed");
31
- mainFrameId = main.frame_id;
32
- await context.connection.send("Debugger.enable", { maxScriptsCacheSize: 4 * 1_024 * 1_024 }, context.sessionId, context.signal);
33
- await delayWithCancellation(context.input.observation_ms, "inspect_electron_page", context.signal);
34
- await report(context.progress, 2, "Capturing Electron structure");
35
- const resources = await captureResources(context, roots, completeness);
36
- const dom = await captureDom(context, roots, completeness);
37
- const normalizedScripts = await captureScripts(context, roots, scripts, completeness);
38
- const after = await context.connection.send("Page.getFrameTree", {}, context.sessionId, context.signal);
39
- const afterMain = await mainFilePath(after, roots);
40
- if (afterMain === undefined)
41
- throw new BrowserObservationError("inspect_web_page", "target_not_allowed");
42
- if (navigationDuringCapture || afterMain !== main.file_path)
43
- throw new BrowserObservationError("inspect_web_page", "target_changed");
44
- await report(context.progress, 3, "Electron inspection complete");
45
- return {
46
- schema_version: 1,
47
- browser: context.discovery.version,
48
- target: {
49
- target_id: context.target.id,
50
- type: context.target.type,
51
- title: context.target.title.slice(0, 16_384),
52
- file_path: main.file_path,
53
- attached: context.target.attached,
54
- },
55
- capture_window: {
56
- started_at: startedAt,
57
- ended_at: new Date().toISOString(),
58
- observation_ms: context.input.observation_ms,
59
- },
60
- completeness: completeness.snapshot(),
61
- frames,
62
- dom,
63
- scripts: normalizedScripts,
64
- resources,
65
- limitations: [
66
- "Only canonical file paths contained by an approved root are retained.",
67
- "REA does not evaluate renderer JavaScript, invoke Electron APIs, navigate, click, or close the page.",
68
- "Script contents require separate source-capture approval and remain byte bounded.",
69
- ],
70
- };
39
+ return await runElectronInspection(context, state, new Date().toISOString());
71
40
  }
72
41
  finally {
73
42
  removeListener();
74
43
  }
75
44
  };
45
+ const runElectronInspection = async (context, state, startedAt) => {
46
+ const frames = await authorizeInitialFrames(context, state);
47
+ const main = frames[0];
48
+ state.mainFrameId = main.frame_id;
49
+ await enableElectronCapture(context);
50
+ await delayWithCancellation(context.input.observation_ms, "inspect_electron_page", context.signal);
51
+ const captured = await captureElectronContent(context, state, frames);
52
+ await assertStableMainFrame(context, state, main.file_path);
53
+ await report(context.progress, 3, "Electron inspection complete");
54
+ return {
55
+ schema_version: 1,
56
+ browser: context.discovery.version,
57
+ target: {
58
+ target_id: context.target.id,
59
+ type: context.target.type,
60
+ title: context.target.title.slice(0, 16_384),
61
+ file_path: main.file_path,
62
+ attached: context.target.attached,
63
+ },
64
+ capture_window: {
65
+ started_at: startedAt,
66
+ ended_at: new Date().toISOString(),
67
+ observation_ms: context.input.observation_ms,
68
+ },
69
+ completeness: state.completeness.snapshot(),
70
+ frames,
71
+ ...captured,
72
+ limitations: state.limitations,
73
+ };
74
+ };
75
+ const authorizeInitialFrames = async (context, state) => {
76
+ await report(context.progress, 1, "Authorizing Electron file frames");
77
+ await context.connection.send("Page.enable", {}, context.sessionId, context.signal);
78
+ const result = await context.connection.send("Page.getFrameTree", {}, context.sessionId, context.signal);
79
+ const frames = await captureFrames(result, state.roots, context.input.limits.max_frames, state.completeness);
80
+ if (frames[0] === undefined)
81
+ throw new BrowserObservationError("inspect_web_page", "target_not_allowed");
82
+ return frames;
83
+ };
84
+ const enableElectronCapture = async (context) => {
85
+ await context.connection.send("Runtime.enable", {}, context.sessionId, context.signal);
86
+ await context.connection.send("Debugger.enable", { maxScriptsCacheSize: 4 * 1_024 * 1_024 }, context.sessionId, context.signal);
87
+ };
88
+ const captureElectronContent = async (context, state, frames) => {
89
+ await report(context.progress, 2, "Capturing Electron structure");
90
+ const frameIds = new Set(frames.map((frame) => frame.frame_id));
91
+ const resources = await captureResources(context, state.roots, state.completeness);
92
+ const dom = await captureDom(context, state.roots, state.completeness);
93
+ const scripts = await captureElectronScripts({
94
+ connection: context.connection,
95
+ sessionId: context.sessionId,
96
+ ...(context.signal === undefined ? {} : { signal: context.signal }),
97
+ request: context.input,
98
+ roots: state.roots,
99
+ scripts: state.scripts,
100
+ executionContextFrames: state.executionContextFrames,
101
+ frameIds,
102
+ completeness: state.completeness,
103
+ });
104
+ const workers = await captureElectronWorkers({
105
+ connection: context.connection,
106
+ ...(context.signal === undefined ? {} : { signal: context.signal }),
107
+ target: context.target,
108
+ request: context.input,
109
+ roots: state.roots,
110
+ frameIds,
111
+ completeness: state.completeness,
112
+ limitations: state.limitations,
113
+ });
114
+ return { dom, scripts, resources, workers };
115
+ };
116
+ const assertStableMainFrame = async (context, state, expectedPath) => {
117
+ const result = await context.connection.send("Page.getFrameTree", {}, context.sessionId, context.signal);
118
+ const afterPath = await mainFilePath(result, state.roots);
119
+ if (afterPath === undefined)
120
+ throw new BrowserObservationError("inspect_web_page", "target_not_allowed");
121
+ if (state.navigationDuringCapture || afterPath !== expectedPath)
122
+ throw new BrowserObservationError("inspect_web_page", "target_changed");
123
+ };
76
124
  const navigatedFrameId = (event) => {
77
125
  const params = recordValue(event.params);
78
126
  if (params === undefined)
@@ -83,33 +131,6 @@ const navigatedFrameId = (event) => {
83
131
  return stringValue(params.frameId);
84
132
  return undefined;
85
133
  };
86
- const ingestScript = (event, scripts, completeness) => {
87
- if (event.method !== "Debugger.scriptParsed")
88
- return;
89
- const value = recordValue(event.params);
90
- const scriptId = stringValue(value?.scriptId);
91
- const rawUrl = stringValue(value?.url);
92
- if (scriptId === undefined || scriptId.length > 256) {
93
- completeness.exclude("scripts", "invalid_protocol_value");
94
- return;
95
- }
96
- if (rawUrl === undefined || rawUrl === "") {
97
- completeness.exclude("scripts", "unattributed_origin");
98
- return;
99
- }
100
- if (scripts.length >= 2_000) {
101
- completeness.drop("scripts");
102
- return;
103
- }
104
- scripts.push({
105
- scriptId,
106
- rawUrl,
107
- hash: (stringValue(value?.hash) ?? "").slice(0, 512),
108
- length: nonnegativeInteger(value?.length),
109
- isModule: value?.isModule === true,
110
- language: boundedText(value?.scriptLanguage, 100),
111
- });
112
- };
113
134
  const captureFrames = async (result, roots, maximum, completeness) => {
114
135
  const frameTree = recordValue(requiredRecord(result).frameTree);
115
136
  if (frameTree === undefined)
@@ -227,82 +248,6 @@ const captureDom = async (context, roots, completeness) => {
227
248
  completeness.truncate("dom");
228
249
  return { total_nodes: total, nodes };
229
250
  };
230
- const captureScripts = async (context, roots, scripts, completeness) => {
231
- let total = 0;
232
- let sourceBytes = 0;
233
- const items = [];
234
- const seen = new Set();
235
- for (const script of scripts) {
236
- const path = await authorizedElectronFile(script.rawUrl, roots);
237
- if (path === undefined) {
238
- completeness.exclude("scripts", "out_of_target_scope");
239
- continue;
240
- }
241
- const identity = {
242
- file_path: path,
243
- cdp_hash: script.hash,
244
- length: script.length,
245
- is_module: script.isModule,
246
- language: script.language,
247
- };
248
- const scriptKey = `electron_script_${digest(identity)}`;
249
- if (seen.has(scriptKey))
250
- continue;
251
- seen.add(scriptKey);
252
- total += 1;
253
- if (items.length >= context.input.limits.max_scripts) {
254
- completeness.drop("scripts");
255
- continue;
256
- }
257
- let source = {
258
- included: false,
259
- reason: "source capture was not approved",
260
- };
261
- if (context.input.include_script_sources) {
262
- if (script.length > context.input.limits.max_script_source_bytes ||
263
- sourceBytes + script.length >
264
- context.input.limits.max_total_script_source_bytes) {
265
- source = {
266
- included: false,
267
- reason: "script source byte limit reached",
268
- };
269
- completeness.truncate("script_sources");
270
- }
271
- else {
272
- const result = requiredRecord(await context.connection.send("Debugger.getScriptSource", { scriptId: script.scriptId }, context.sessionId, context.signal));
273
- const text = stringValue(result.scriptSource) ?? "";
274
- const bytes = Buffer.byteLength(text);
275
- if (bytes > context.input.limits.max_script_source_bytes ||
276
- sourceBytes + bytes >
277
- context.input.limits.max_total_script_source_bytes) {
278
- source = {
279
- included: false,
280
- reason: "script source byte limit reached",
281
- };
282
- completeness.truncate("script_sources");
283
- }
284
- else {
285
- source = {
286
- included: true,
287
- artifact: createWebTextArtifact(text, "text/javascript"),
288
- };
289
- sourceBytes += bytes;
290
- }
291
- }
292
- }
293
- items.push({
294
- script_key: scriptKey,
295
- ...identity,
296
- source,
297
- });
298
- }
299
- if (!context.input.include_script_sources)
300
- completeness.exclude("script_sources", "not_approved", total);
301
- return {
302
- total,
303
- items: items.sort((left, right) => left.script_key.localeCompare(right.script_key)),
304
- };
305
- };
306
251
  const mainFilePath = async (result, roots) => {
307
252
  const frameTree = recordValue(requiredRecord(result).frameTree);
308
253
  const frame = recordValue(frameTree?.frame);
@@ -314,7 +259,6 @@ const integer = (value, fallback) => {
314
259
  const number = numberValue(value);
315
260
  return number === undefined ? fallback : Math.trunc(number);
316
261
  };
317
- const nonnegativeInteger = (value) => Math.max(0, integer(value, 0));
318
262
  const digest = (value) => {
319
263
  const encoded = canonicalize(value);
320
264
  if (encoded === undefined)
@@ -108,7 +108,11 @@ export class CdpElectronProvider {
108
108
  }
109
109
  finally {
110
110
  if (targetSession !== undefined)
111
- await closeCdpTargetSession(targetSession, ["Debugger", "Page"]);
111
+ await closeCdpTargetSession(targetSession, [
112
+ "Debugger",
113
+ "Runtime",
114
+ "Page",
115
+ ]);
112
116
  }
113
117
  }
114
118
  }
@@ -0,0 +1,69 @@
1
+ import { CdpCaptureCompleteness } from "./CdpCaptureCompleteness.js";
2
+ import { boundedText, numberValue, recordValue, stringValue, } from "./CdpCaptureValues.js";
3
+ /** Retain only bounded script/frame metadata from passive Electron events. */
4
+ export const ingestElectronScriptEvent = (input) => {
5
+ if (input.event.method === "Runtime.executionContextsCleared") {
6
+ input.executionContextFrames.clear();
7
+ return;
8
+ }
9
+ if (input.event.method === "Runtime.executionContextDestroyed") {
10
+ const parameters = recordValue(input.event.params);
11
+ const key = executionContextKey(parameters?.executionContextId);
12
+ if (key !== null)
13
+ input.executionContextFrames.delete(key);
14
+ return;
15
+ }
16
+ if (input.event.method === "Runtime.executionContextCreated") {
17
+ retainExecutionContext(input.event, input.executionContextFrames, input.completeness);
18
+ return;
19
+ }
20
+ if (input.event.method !== "Debugger.scriptParsed")
21
+ return;
22
+ const value = recordValue(input.event.params);
23
+ const scriptId = stringValue(value?.scriptId);
24
+ const rawUrl = stringValue(value?.url);
25
+ if (scriptId === undefined || scriptId.length > 256) {
26
+ input.completeness.exclude("scripts", "invalid_protocol_value");
27
+ return;
28
+ }
29
+ if (rawUrl === undefined || rawUrl === "") {
30
+ input.completeness.exclude("scripts", "unattributed_origin");
31
+ return;
32
+ }
33
+ if (input.scripts.length >= 2_000) {
34
+ input.completeness.drop("scripts");
35
+ return;
36
+ }
37
+ input.scripts.push({
38
+ scriptId,
39
+ rawUrl,
40
+ hash: (stringValue(value?.hash) ?? "").slice(0, 512),
41
+ length: nonnegativeInteger(value?.length),
42
+ isModule: value?.isModule === true,
43
+ language: boundedText(value?.scriptLanguage, 100),
44
+ executionContextKey: executionContextKey(value?.executionContextId),
45
+ });
46
+ };
47
+ const retainExecutionContext = (event, frames, completeness) => {
48
+ const parameters = recordValue(event.params);
49
+ const runtimeContext = recordValue(parameters?.context);
50
+ const contextKey = executionContextKey(runtimeContext?.id);
51
+ const frameId = stringValue(recordValue(runtimeContext?.auxData)?.frameId);
52
+ if (contextKey === null || frameId === undefined || frameId.length > 256)
53
+ return;
54
+ if (frames.size >= 2_000 && !frames.has(contextKey)) {
55
+ completeness.truncate("scripts");
56
+ return;
57
+ }
58
+ frames.set(contextKey, frameId);
59
+ };
60
+ const nonnegativeInteger = (value) => {
61
+ const number = numberValue(value);
62
+ return number === undefined ? 0 : Math.max(0, Math.trunc(number));
63
+ };
64
+ const executionContextKey = (value) => {
65
+ const identifier = numberValue(value);
66
+ return identifier !== undefined && Number.isSafeInteger(identifier)
67
+ ? String(identifier)
68
+ : null;
69
+ };
@@ -0,0 +1,94 @@
1
+ import { createHash } from "node:crypto";
2
+ import canonicalize from "canonicalize";
3
+ import { createWebTextArtifact } from "../domain/webContentArtifact.js";
4
+ import { CdpCaptureCompleteness } from "./CdpCaptureCompleteness.js";
5
+ import { requiredRecord, stringValue } from "./CdpCaptureValues.js";
6
+ import { authorizedElectronFile } from "./ElectronFileScope.js";
7
+ /** Normalize root-confined script metadata and optionally approved source. */
8
+ export const captureElectronScripts = async (input) => {
9
+ let total = 0;
10
+ let sourceBytes = 0;
11
+ const items = [];
12
+ const seen = new Set();
13
+ for (const script of input.scripts) {
14
+ const path = await authorizedElectronFile(script.rawUrl, input.roots);
15
+ if (path === undefined) {
16
+ input.completeness.exclude("scripts", "out_of_target_scope");
17
+ continue;
18
+ }
19
+ const mappedFrame = script.executionContextKey === null
20
+ ? undefined
21
+ : input.executionContextFrames.get(script.executionContextKey);
22
+ const identity = {
23
+ frame_id: mappedFrame !== undefined && input.frameIds.has(mappedFrame)
24
+ ? mappedFrame
25
+ : null,
26
+ file_path: path,
27
+ cdp_hash: script.hash,
28
+ length: script.length,
29
+ is_module: script.isModule,
30
+ language: script.language,
31
+ };
32
+ const scriptKey = `electron_script_${digest(identity)}`;
33
+ if (seen.has(scriptKey))
34
+ continue;
35
+ seen.add(scriptKey);
36
+ total += 1;
37
+ if (items.length >= input.request.limits.max_scripts) {
38
+ input.completeness.drop("scripts");
39
+ continue;
40
+ }
41
+ const capturedSource = await captureScriptSource(input, script, sourceBytes);
42
+ sourceBytes += capturedSource.bytes;
43
+ items.push({
44
+ script_key: scriptKey,
45
+ ...identity,
46
+ source: capturedSource.source,
47
+ });
48
+ }
49
+ if (!input.request.include_script_sources)
50
+ input.completeness.exclude("script_sources", "not_approved", total);
51
+ return {
52
+ total,
53
+ items: items.sort((left, right) => left.script_key.localeCompare(right.script_key)),
54
+ };
55
+ };
56
+ const captureScriptSource = async (input, script, sourceBytes) => {
57
+ if (!input.request.include_script_sources)
58
+ return {
59
+ source: {
60
+ included: false,
61
+ reason: "source capture was not approved",
62
+ },
63
+ bytes: 0,
64
+ };
65
+ if (sourceLimitReached(input.request.limits, script.length, sourceBytes))
66
+ return sourceLimitResult(input.completeness);
67
+ const result = requiredRecord(await input.connection.send("Debugger.getScriptSource", { scriptId: script.scriptId }, input.sessionId, input.signal));
68
+ const text = stringValue(result.scriptSource) ?? "";
69
+ const bytes = Buffer.byteLength(text);
70
+ if (sourceLimitReached(input.request.limits, bytes, sourceBytes))
71
+ return sourceLimitResult(input.completeness);
72
+ return {
73
+ source: {
74
+ included: true,
75
+ artifact: createWebTextArtifact(text, "text/javascript"),
76
+ },
77
+ bytes,
78
+ };
79
+ };
80
+ const sourceLimitReached = (limits, bytes, previousBytes) => bytes > limits.max_script_source_bytes ||
81
+ previousBytes + bytes > limits.max_total_script_source_bytes;
82
+ const sourceLimitResult = (completeness) => {
83
+ completeness.truncate("script_sources");
84
+ return {
85
+ source: { included: false, reason: "script source byte limit reached" },
86
+ bytes: 0,
87
+ };
88
+ };
89
+ const digest = (value) => {
90
+ const encoded = canonicalize(value);
91
+ if (encoded === undefined)
92
+ throw new TypeError("Expected canonical JSON");
93
+ return createHash("sha256").update(encoded).digest("hex");
94
+ };
@@ -0,0 +1,56 @@
1
+ import { CdpCaptureCompleteness } from "./CdpCaptureCompleteness.js";
2
+ import { boundedText, recordValue, recordsValue, stringValue, } from "./CdpCaptureValues.js";
3
+ import { optionalCdpCommand } from "./CdpOptionalCommand.js";
4
+ import { authorizedElectronFile } from "./ElectronFileScope.js";
5
+ /** Inventory root-confined worker targets without attaching to them. */
6
+ export const captureElectronWorkers = async (input) => {
7
+ const result = await optionalCdpCommand({
8
+ connection: input.connection,
9
+ sessionId: undefined,
10
+ ...(input.signal === undefined ? {} : { signal: input.signal }),
11
+ }, "Target.getTargets", {}, input.limitations);
12
+ if (result === undefined) {
13
+ input.completeness.unavailable("workers");
14
+ return [];
15
+ }
16
+ const workers = [];
17
+ for (const target of recordsValue(recordValue(result)?.targetInfos)) {
18
+ const type = stringValue(target.type) ?? "";
19
+ if (!type.includes("worker"))
20
+ continue;
21
+ const openerTargetId = boundedTargetField(target.openerId);
22
+ const parentFrameId = boundedTargetField(target.parentFrameId);
23
+ if (openerTargetId !== input.target.id &&
24
+ (parentFrameId === null || !input.frameIds.has(parentFrameId))) {
25
+ input.completeness.exclude("workers", "out_of_target_scope");
26
+ continue;
27
+ }
28
+ const path = await authorizedElectronFile(stringValue(target.url) ?? "", input.roots);
29
+ if (path === undefined) {
30
+ input.completeness.exclude("workers", "out_of_target_scope");
31
+ continue;
32
+ }
33
+ if (workers.length >= input.request.limits.max_workers) {
34
+ input.completeness.truncate("workers");
35
+ continue;
36
+ }
37
+ const targetId = boundedTargetField(target.targetId);
38
+ if (targetId === null) {
39
+ input.completeness.exclude("workers", "invalid_protocol_value");
40
+ continue;
41
+ }
42
+ workers.push({
43
+ target_id: targetId,
44
+ type: type.slice(0, 100),
45
+ file_path: path,
46
+ attached: target.attached === true,
47
+ opener_target_id: openerTargetId,
48
+ parent_frame_id: parentFrameId,
49
+ });
50
+ }
51
+ return workers.sort((left, right) => left.target_id.localeCompare(right.target_id));
52
+ };
53
+ const boundedTargetField = (value) => {
54
+ const field = boundedText(value, 256);
55
+ return field === null || field === "" ? null : field;
56
+ };
@@ -5,7 +5,7 @@ import { CdpCaptureEvents } from "./CdpCaptureEvents.js";
5
5
  import { captureStorage } from "./CdpCaptureStorage.js";
6
6
  import { optionalCdpCommand } from "./CdpOptionalCommand.js";
7
7
  import { captureAccessibility, captureDom, captureFrames, captureResources, mainFrameUrl, } from "./CdpCaptureDocuments.js";
8
- import { allowedSanitizedUrl, delayWithCancellation, isHttpUrl, recordValue, recordsValue, requiredRecord, sourceExcluded, sourceResult, stringValue, } from "./CdpCaptureValues.js";
8
+ import { allowedSanitizedUrl, boundedText, delayWithCancellation, isHttpUrl, recordValue, recordsValue, requiredRecord, sourceExcluded, sourceResult, stringValue, } from "./CdpCaptureValues.js";
9
9
  /** Capture and normalize one attached page without evaluating page JavaScript. */
10
10
  export const capturePage = async (context) => {
11
11
  const allowedOrigins = new Set(context.input.allowed_origins);
@@ -59,7 +59,7 @@ const captureAuthorizedPage = async (state) => {
59
59
  const accessibility = await accessibilityForFrames(state, frames);
60
60
  if (accessibility.total > accessibility.nodes.length)
61
61
  state.events.completeness.truncate("accessibility");
62
- const scripts = await captureScripts(context, state.events, resourceCapture.items, resources);
62
+ const scripts = await captureScripts(context, state.events, resourceCapture.items, resources, new Set(frames.map((frame) => frame.frame_id)));
63
63
  const workerCapture = await captureWorkers(state, new Set(frames.map((frame) => frame.frame_id)));
64
64
  if (workerCapture.total > workerCapture.items.length)
65
65
  state.events.completeness.truncate("workers");
@@ -220,7 +220,7 @@ const accessibilityForFrames = async (state, frames) => {
220
220
  events.completeness.truncate("accessibility");
221
221
  return capture;
222
222
  };
223
- const captureScripts = async (context, events, rawResources, resources) => {
223
+ const captureScripts = async (context, events, rawResources, resources, frameIds) => {
224
224
  let totalSourceBytes = 0;
225
225
  const drafts = [];
226
226
  for (const script of events.scripts.values()) {
@@ -252,6 +252,7 @@ const captureScripts = async (context, events, rawResources, resources) => {
252
252
  }
253
253
  }
254
254
  const inventoryScript = {
255
+ frame_id: events.frameForScript(script, frameIds),
255
256
  url: script.url,
256
257
  cdp_hash: script.hash,
257
258
  length: script.length,
@@ -276,7 +277,8 @@ const captureScripts = async (context, events, rawResources, resources) => {
276
277
  base: stableWebScriptKey(draft.item),
277
278
  }));
278
279
  keyed.sort((left, right) => left.base.localeCompare(right.base) ||
279
- sourceDigest(left.item.source).localeCompare(sourceDigest(right.item.source)));
280
+ sourceDigest(left.item.source).localeCompare(sourceDigest(right.item.source)) ||
281
+ (left.item.frame_id ?? "").localeCompare(right.item.frame_id ?? ""));
280
282
  const totals = new Map();
281
283
  for (const { base } of keyed)
282
284
  totals.set(base, (totals.get(base) ?? 0) + 1);
@@ -349,6 +351,8 @@ const captureWorkers = async (state, frameIds) => {
349
351
  url: url.url,
350
352
  origin: url.origin,
351
353
  attached: target.attached === true,
354
+ opener_target_id: boundedText(target.openerId, 256) ?? null,
355
+ parent_frame_id: boundedText(target.parentFrameId, 256) ?? null,
352
356
  });
353
357
  }
354
358
  return {
@@ -7,8 +7,10 @@ import { CLI_COMMAND_NAMES } from "./cliCommandNames.js";
7
7
  export { CLI_COMMAND_NAMES } from "./cliCommandNames.js";
8
8
  const toolCatalog = TOOL_CONTRACTS.map((contract) => ({
9
9
  name: contract.name,
10
+ title: contract.title,
10
11
  surface: contract.kind,
11
12
  description: contract.description,
13
+ effects: { ...contract.effects },
12
14
  annotations: contract.annotations,
13
15
  input_schema: z.toJSONSchema(contract.inputSchema, {
14
16
  unrepresentable: "any",
@@ -79,9 +81,10 @@ export const CATALOG_IDENTITY = {
79
81
  resource_templates: resourceTemplateCatalog,
80
82
  }),
81
83
  },
82
- tools: toolCatalog.map(({ name, surface, annotations }) => ({
84
+ tools: toolCatalog.map(({ name, surface, effects, annotations }) => ({
83
85
  name,
84
86
  surface,
87
+ effects,
85
88
  annotations: {
86
89
  read_only: annotations.readOnlyHint ?? false,
87
90
  destructive: annotations.destructiveHint ?? false,