rea-agents 3.0.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -16
- package/dist/application/AnalysisContextQueries.js +21 -9
- package/dist/application/ApplicationWorkflowEvidenceResolver.js +19 -11
- package/dist/application/ArtifactInventory/policy.js +17 -0
- package/dist/application/ArtifactInventory/reader.js +1 -4
- package/dist/application/ArtifactInventory/scanCanonical.js +0 -3
- package/dist/application/ArtifactInventory/scanReader.js +7 -7
- package/dist/application/ArtifactInventory/types.js +1 -5
- package/dist/application/BinarySession.js +14 -16
- package/dist/application/BinarySessionRecords.js +31 -5
- package/dist/application/BinarySessionStatus.js +11 -5
- package/dist/application/BrowserEvidence.js +3 -5
- package/dist/application/CapabilityClientRequirements.js +26 -0
- package/dist/application/CapabilityInventory.js +134 -63
- package/dist/application/CrossVersionInventory.js +7 -6
- package/dist/application/ElectronActiveEvidence.js +55 -0
- package/dist/application/ElectronActiveObservationPort.js +1 -0
- package/dist/application/ElectronActiveObservationService.js +81 -0
- package/dist/application/EnhancedTools.js +22 -20
- package/dist/application/EvidenceBundleCommands.js +5 -1
- package/dist/application/EvidenceLedger.js +8 -1
- package/dist/application/JavaScriptReplayPermission.js +1 -1
- package/dist/application/JavaScriptReplayService.js +23 -15
- package/dist/application/ManagedRuntimeCorrelationService.js +9 -5
- package/dist/application/NodeRuntimeCharacterizationService.js +8 -9
- package/dist/application/ProcessCaptureLifecycle.js +5 -0
- package/dist/application/ProcessCli.js +2 -1
- package/dist/application/ProcessHarness.js +8 -3
- package/dist/application/ProcessNormalization.js +2 -1
- package/dist/application/ProjectPermissionStore.js +1 -0
- package/dist/application/ReferenceSourceImport.js +2 -2
- package/dist/application/SetupClientConfiguration.js +6 -1
- package/dist/application/WindowsCapabilities.js +5 -4
- package/dist/artifacts/ArtifactProvider.js +8 -10
- package/dist/artifacts/SafeOutputTree.js +7 -8
- package/dist/browser/CdpCaptureStorageFingerprints.js +10 -7
- package/dist/browser/CdpElectronInspection.js +3 -1
- package/dist/browser/JavaScriptRuntimeScope.js +5 -0
- package/dist/browser/PlaywrightElectronActiveActions.js +324 -0
- package/dist/browser/PlaywrightElectronActiveProvider.js +294 -0
- package/dist/browser/PlaywrightScenarioBrowser.js +4 -4
- package/dist/browser/PlaywrightScenarioSession.js +4 -4
- package/dist/browser/SensitiveTextCapture.js +1 -1
- package/dist/browser/V8InspectorCaptureProjection.js +1 -0
- package/dist/browser/V8InspectorProvider.js +3 -3
- package/dist/catalogIdentity.js +12 -6
- package/dist/cli/managedCommands.js +1 -1
- package/dist/cliApplicationCommands.js +2 -2
- package/dist/cliBrowserAdvancedCommands.js +11 -5
- package/dist/cliBrowserCommands.js +14 -7
- package/dist/cliCommandNames.js +1 -0
- package/dist/cliElectronCommands.js +46 -4
- package/dist/cliJavaScriptRuntimeCommands.js +14 -6
- package/dist/cliObservationOptions.js +4 -0
- package/dist/cliPolicyCommands.js +1 -0
- package/dist/config/browserScenario.js +77 -0
- package/dist/config/electronAutomation.js +35 -0
- package/dist/config/environment.js +6 -0
- package/dist/config/javascriptReplay.js +41 -0
- package/dist/config/managedRuntime.js +25 -0
- package/dist/config/parseConfig.js +56 -200
- package/dist/config/parsers.js +10 -1
- package/dist/config/passiveObservation.js +157 -0
- package/dist/config/processCapture.js +47 -0
- package/dist/contracts/applicationToolContracts.js +2 -5
- package/dist/contracts/applicationWorkflowInputContracts.js +86 -96
- package/dist/contracts/artifactToolContracts.js +27 -34
- package/dist/contracts/browserToolContracts.js +2 -2
- package/dist/contracts/electronToolContracts.js +30 -1
- package/dist/contracts/enhancedInputs.js +13 -29
- package/dist/contracts/functionWorkflowToolContracts.js +2 -3
- package/dist/contracts/managedToolContracts.js +4 -9
- package/dist/contracts/managedWorkflowExamples.js +12 -9
- package/dist/contracts/managedWorkflowToolContracts.js +30 -27
- package/dist/contracts/nativeToolContracts.js +2 -3
- package/dist/contracts/sessionToolSchemas.js +61 -0
- package/dist/contracts/toolContractExamples.js +1 -0
- package/dist/contracts/toolContractHelpers.js +23 -0
- package/dist/contracts/toolContracts.js +5 -77
- package/dist/contracts/toolEffects.js +15 -0
- package/dist/contracts/toolOutputSchemaGroups.js +17 -2
- package/dist/contracts/toolOutputSchemaPrimitives.js +76 -26
- package/dist/contracts/toolSchemaMetadata.js +128 -0
- package/dist/doctorRuntime.js +1 -1
- package/dist/domain/androidApplication.js +2 -0
- package/dist/domain/appleApplication.js +2 -0
- package/dist/domain/binaryTarget.js +7 -5
- package/dist/domain/binaryTargetTypes.js +1 -0
- package/dist/domain/browserCaptureComparison.js +21 -69
- package/dist/domain/browserObservation.js +22 -9
- package/dist/domain/bytecodeProvider.js +33 -35
- package/dist/domain/callPath.js +3 -2
- package/dist/domain/conformanceReplay.js +3 -3
- package/dist/domain/conformanceTrustGate.js +151 -16
- package/dist/domain/customProtocolCapture.js +3 -3
- package/dist/domain/electronActiveObservation.js +277 -0
- package/dist/domain/electronObservation.js +15 -11
- package/dist/domain/electronStaticAnalysisValues.js +4 -1
- package/dist/domain/eventProcessTree.js +82 -79
- package/dist/domain/functionComparisonNormalization.js +15 -8
- package/dist/domain/functionDossierEvidence.js +9 -4
- package/dist/domain/hopperValues.js +68 -71
- package/dist/domain/inputIssueProjection.js +4 -1
- package/dist/domain/investigationWorkspace.js +16 -15
- package/dist/domain/javascriptExportShapeComparisonSchemas.js +2 -2
- package/dist/domain/javascriptReplay.js +79 -63
- package/dist/domain/javascriptRuntimeObservation.js +1 -0
- package/dist/domain/javascriptRuntimeReconciliationActive.js +89 -0
- package/dist/domain/javascriptRuntimeReconciliationGraph.js +11 -10
- package/dist/domain/javascriptRuntimeReconciliationMatching.js +57 -49
- package/dist/domain/javascriptRuntimeReconciliationParsing.js +5 -1
- package/dist/domain/javascriptRuntimeReconciliationResult.js +21 -14
- package/dist/domain/javascriptRuntimeReconciliationSchemas.js +1 -1
- package/dist/domain/knownPageCoverage.js +15 -7
- package/dist/domain/nativeApiBoundary.js +15 -7
- package/dist/domain/nodeRuntimeCharacterization.js +14 -13
- package/dist/domain/processCaptureReactiveValidation.js +8 -3
- package/dist/domain/processCaptureValidation.js +7 -3
- package/dist/domain/processComparison.js +12 -7
- package/dist/domain/processObservation.js +5 -6
- package/dist/domain/processReactiveMatching.js +14 -7
- package/dist/domain/processReactiveTransition.js +9 -3
- package/dist/domain/processScenario.js +6 -3
- package/dist/domain/processTraceComparison.js +3 -2
- package/dist/domain/protocolCapture.js +8 -6
- package/dist/domain/reconstructionReadinessSchemas.js +30 -2
- package/dist/domain/referenceSourceImportParsing.js +2 -2
- package/dist/domain/replayMachineRuntime.js +5 -3
- package/dist/domain/sourceToBundleComparison.js +2 -2
- package/dist/domain/sourceToBundleComparisonSchemas.js +8 -8
- package/dist/domain/sourceToBundleSignals.js +7 -1
- package/dist/domain/staticRuntimeCorrelation.js +7 -5
- package/dist/domain/webBundleAnalysis.js +14 -14
- package/dist/domain/webCaptureDiff.js +15 -12
- package/dist/dotnet/ManagedNativeBoundaryInspector.js +2 -3
- package/dist/generatedMcpToolCatalog.js +1 -1
- package/dist/generatedPackageMetadata.js +1 -1
- package/dist/ghidra/GhidraClient.js +1 -1
- package/dist/ghidra/GhidraClientStartup.js +1 -1
- package/dist/ghidra/GhidraFunctionValues.js +17 -15
- package/dist/ghidra/GhidraInventoryValues.js +57 -47
- package/dist/ghidra/GhidraProvider.js +18 -10
- package/dist/ghidra/GhidraProviderCapabilities.js +5 -1
- package/dist/ghidra/GhidraSocketConnection.js +1 -1
- package/dist/ghidra/protocol.js +19 -42
- package/dist/hopper/BridgeLauncher.js +3 -1
- package/dist/hopper/HopperAnalysisProfile.js +2 -4
- package/dist/hopper/HopperCleanup.js +5 -6
- package/dist/hopper/HopperClient.js +1 -1
- package/dist/hopper/HopperProvider.js +0 -14
- package/dist/hopper/LinuxPrivateDisplayProbe.js +16 -10
- package/dist/main/reload.js +1 -6
- package/dist/main/state.js +0 -14
- package/dist/main/transport.js +30 -37
- package/dist/main.js +17 -15
- package/dist/mcpDoctor.js +2 -24
- package/dist/process/PrivateRuntimeRoot.js +17 -0
- package/dist/process/ProcessOwnership.js +64 -128
- package/dist/process/ProcessOwnershipIdentity.js +13 -0
- package/dist/process/ProcessOwnershipObservation.js +88 -0
- package/dist/process/ProcessOwnershipProcessTree.js +22 -0
- package/dist/process/ProviderDeadline.js +8 -12
- package/dist/process/WindowsAuthority.js +32 -0
- package/dist/reference/ReferenceSourceReader.js +3 -3
- package/dist/reference/ReferenceSourceReaderEntries.js +2 -2
- package/dist/replay/JavaScriptReplayWorker.js +95 -11
- package/dist/replay/JavaScriptReplayWorkerTypes.js +1 -0
- package/dist/replay/LinuxJavaScriptReplayRunner.js +10 -15
- package/dist/replay/ReplayOutcome.js +26 -15
- package/dist/replay/ReplayWorkerProtocol.js +34 -29
- package/dist/server/createServer.js +158 -94
- package/dist/server/mcpClientMetadata.js +32 -0
- package/dist/server/registerApplicationTools/characterization.js +6 -13
- package/dist/server/registerApplicationTools/compareExportShapes.js +2 -7
- package/dist/server/registerApplicationTools/compareSourceToBundle.js +2 -7
- package/dist/server/registerApplicationTools/compareVersions.js +3 -8
- package/dist/server/registerApplicationTools/controlledReplay.js +6 -11
- package/dist/server/registerApplicationTools/coverage.js +13 -19
- package/dist/server/registerApplicationTools/obligationLedger.js +1 -6
- package/dist/server/registerApplicationTools/readiness.js +1 -6
- package/dist/server/registerApplicationTools/traceFeature.js +3 -8
- package/dist/server/registerApplicationTools/traceSemantics.js +1 -6
- package/dist/server/registerArtifactComparisonTool.js +12 -16
- package/dist/server/registerBrowserScenarioTool.js +1 -6
- package/dist/server/registerBrowserTools.js +37 -79
- package/dist/server/registerBundleComparisonTool.js +10 -14
- package/dist/server/registerCloseLifecycleTool.js +6 -14
- package/dist/server/registerElectronTools.js +30 -47
- package/dist/server/registerEnhancedTools.js +114 -122
- package/dist/server/registerEvidenceTools.js +5 -9
- package/dist/server/registerFunctionComparisonTool.js +11 -15
- package/dist/server/registerInvestigationTools.js +41 -57
- package/dist/server/registerJavaScriptRuntimeObservationTools.js +13 -27
- package/dist/server/registerManagedWorkflowTools/compareManagedMembers.js +4 -9
- package/dist/server/registerManagedWorkflowTools/contract.js +2 -3
- package/dist/server/registerManagedWorkflowTools/importManagedReconstruction.js +3 -8
- package/dist/server/registerManagedWorkflowTools/planManagedRuntimeCorrelation.js +3 -8
- package/dist/server/registerManagedWorkflowTools/projectManagedApplicationGraph.js +8 -13
- package/dist/server/registerManagedWorkflowTools/verifyManagedNativeBoundaries.js +4 -9
- package/dist/server/registerOfficialTools.js +8 -9
- package/dist/server/registerProcessComparisonTool.js +19 -24
- package/dist/server/registerReplayMachineTool.js +4 -10
- package/dist/server/registerSessionRecordTools.js +69 -40
- package/dist/server/registerSessionStatusTool.js +13 -28
- package/dist/server/registerSessionTools.js +36 -52
- package/dist/server/sessionAvailabilityPolicy.js +2 -1
- package/dist/server/sessionToolPolicies.js +1 -5
- package/dist/server/toolRegistrationOptions.js +4 -192
- package/dist/server/toolResult.js +2 -18
- package/package.json +22 -14
- package/scripts/electron-active-hook-boundaries.cjs +394 -0
- package/scripts/electron-active-hook.cjs +501 -0
- package/scripts/rea.mjs +1 -11
- package/skills/reverse-engineer-anything/SKILL.md +2 -2
- package/dist/server/DynamicToolAvailability.js +0 -70
- package/dist/server/LazyToolCatalog.js +0 -134
- package/dist/server/hydrateServerTools.js +0 -191
- package/dist/server/toolInputValidation.js +0 -77
- package/scripts/package-runner-bootstrap.mjs +0 -72
|
@@ -6,6 +6,10 @@ import { err, ok } from "../domain/result.js";
|
|
|
6
6
|
import { MANAGED_WORKFLOW_PROVIDER } from "./InvestigationProviders.js";
|
|
7
7
|
import { digestJson } from "./JavaScriptReplayPlanning.js";
|
|
8
8
|
const OPERATION = "plan_managed_runtime_correlation";
|
|
9
|
+
/** A managed-runtime policy that grants no runtime-correlation authority. */
|
|
10
|
+
export const MANAGED_RUNTIME_DISABLED = {
|
|
11
|
+
status: "disabled",
|
|
12
|
+
};
|
|
9
13
|
/** Create a separately authorized, non-executing managed runtime plan Evidence. */
|
|
10
14
|
export const planManagedRuntimeCorrelationEvidence = async (dependencies, rawInput) => {
|
|
11
15
|
const parsed = managedRuntimeCorrelationInputSchema.safeParse(rawInput);
|
|
@@ -15,12 +19,12 @@ export const planManagedRuntimeCorrelationEvidence = async (dependencies, rawInp
|
|
|
15
19
|
};
|
|
16
20
|
/** Plan runtime correlation from input parsed by a trusted adapter. */
|
|
17
21
|
export const planManagedRuntimeCorrelationEvidenceValidated = async (dependencies, input) => {
|
|
18
|
-
|
|
22
|
+
const policy = dependencies.policy();
|
|
23
|
+
if (policy.status === "disabled")
|
|
19
24
|
return err(new AnalysisCapabilityUnavailableError(MANAGED_WORKFLOW_PROVIDER.id, OPERATION, "managed runtime correlation is disabled; configure exact roots and runtime executable before enabling it"));
|
|
20
25
|
if (dependencies.authority === undefined)
|
|
21
26
|
return err(new AnalysisCapabilityUnavailableError(MANAGED_WORKFLOW_PROVIDER.id, OPERATION, "managed runtime permission policy is not configured"));
|
|
22
|
-
let staticEvidence;
|
|
23
|
-
let artifactPath;
|
|
27
|
+
let staticEvidence, artifactPath;
|
|
24
28
|
try {
|
|
25
29
|
staticEvidence = parseEvidence(input.static_members);
|
|
26
30
|
artifactPath = artifactPathFor(staticEvidence);
|
|
@@ -31,7 +35,7 @@ export const planManagedRuntimeCorrelationEvidenceValidated = async (dependencie
|
|
|
31
35
|
const authorized = await dependencies.authority.explain({
|
|
32
36
|
capability: "managed_runtime",
|
|
33
37
|
roots: [artifactPath],
|
|
34
|
-
executables: [
|
|
38
|
+
executables: [policy.executablePath],
|
|
35
39
|
environment_names: [],
|
|
36
40
|
network: "none",
|
|
37
41
|
mount: false,
|
|
@@ -44,7 +48,7 @@ export const planManagedRuntimeCorrelationEvidenceValidated = async (dependencie
|
|
|
44
48
|
cause: authorized.error,
|
|
45
49
|
}));
|
|
46
50
|
try {
|
|
47
|
-
const result = planManagedRuntimeCorrelation(input,
|
|
51
|
+
const result = planManagedRuntimeCorrelation(input, policy.executablePath, authorized.value.grant_id);
|
|
48
52
|
return ok(createEvidence({
|
|
49
53
|
path: result.static_observation.artifact_path,
|
|
50
54
|
sha256: result.static_observation.artifact_sha256,
|
|
@@ -3,7 +3,7 @@ import { z } from "zod";
|
|
|
3
3
|
import { nodeCharacterizationExecutionInputSchema, nodeCharacterizationExecutionOutputSchema, nodeCharacterizationPreparationInputSchema, nodeCharacterizationPreparationOutputSchema, } from "../domain/nodeRuntimeCharacterization.js";
|
|
4
4
|
import { AnalysisInputError, AnalysisProtocolError, ReplayPlanStaleError, } from "../domain/errors.js";
|
|
5
5
|
import { createEvidence } from "../domain/evidence.js";
|
|
6
|
-
import { controlledReplayOutputSchema } from "../domain/javascriptReplay.js";
|
|
6
|
+
import { controlledReplayOutputSchema, } from "../domain/javascriptReplay.js";
|
|
7
7
|
import { createRuntimeCharacterizationPlan, } from "../domain/runtimeCharacterization.js";
|
|
8
8
|
import { err, ok } from "../domain/result.js";
|
|
9
9
|
import { jsonValueSchema } from "../domain/jsonValue.js";
|
|
@@ -37,7 +37,7 @@ export const executeNodeCharacterization = async (dependencies, rawInput, option
|
|
|
37
37
|
if (prepared.value.plan.plan_sha256 !== parsed.data.approved_plan_sha256)
|
|
38
38
|
return err(new ReplayPlanStaleError(parsed.data.approved_plan_sha256, prepared.value.plan.plan_sha256));
|
|
39
39
|
const plannedReplay = controlledReplayOutputSchema.parse(prepared.value.replay);
|
|
40
|
-
if (plannedReplay.phase !== "plan"
|
|
40
|
+
if (plannedReplay.phase !== "plan")
|
|
41
41
|
return err(new AnalysisProtocolError("Node characterization preparation did not produce a replay plan"));
|
|
42
42
|
const host = new InstrumentedJavaScriptReplayHost(dependencies.host, parsed.data.preparation.instrumentation);
|
|
43
43
|
const executed = await runControlledReplayValidated({ ...dependencies, host }, {
|
|
@@ -48,6 +48,9 @@ export const executeNodeCharacterization = async (dependencies, rawInput, option
|
|
|
48
48
|
}, options);
|
|
49
49
|
if (!executed.ok)
|
|
50
50
|
return executed;
|
|
51
|
+
const replayOutput = controlledReplayOutputSchema.parse(executed.value);
|
|
52
|
+
if (replayOutput.phase !== "execute")
|
|
53
|
+
return err(new AnalysisProtocolError("Node characterization execution omitted replay Evidence"));
|
|
51
54
|
if (host.instrumentation === null)
|
|
52
55
|
return err(new AnalysisProtocolError("Node characterization execution omitted instrumentation evidence"));
|
|
53
56
|
return ok(jsonValueSchema.parse(nodeCharacterizationExecutionOutputSchema.parse({
|
|
@@ -56,8 +59,8 @@ export const executeNodeCharacterization = async (dependencies, rawInput, option
|
|
|
56
59
|
plan: prepared.value.plan,
|
|
57
60
|
transformation: host.instrumentation.manifest,
|
|
58
61
|
transformation_evidence: prepared.value.transformation_evidence,
|
|
59
|
-
evidence: createCharacterizationEvidence(prepared.value,
|
|
60
|
-
replay:
|
|
62
|
+
evidence: createCharacterizationEvidence(prepared.value, replayOutput),
|
|
63
|
+
replay: replayOutput,
|
|
61
64
|
})));
|
|
62
65
|
};
|
|
63
66
|
const prepareValidated = async (dependencies, input, options) => {
|
|
@@ -66,9 +69,7 @@ const prepareValidated = async (dependencies, input, options) => {
|
|
|
66
69
|
if (!replay.ok)
|
|
67
70
|
return replay;
|
|
68
71
|
const output = controlledReplayOutputSchema.parse(replay.value);
|
|
69
|
-
if (output.phase !== "plan" ||
|
|
70
|
-
output.plan === null ||
|
|
71
|
-
host.instrumentation === null)
|
|
72
|
+
if (output.phase !== "plan" || host.instrumentation === null)
|
|
72
73
|
return err(new AnalysisProtocolError("Node characterization preparation omitted committed plan evidence"));
|
|
73
74
|
const module = output.plan.left.modules.find(({ alias }) => alias === input.selected_alias);
|
|
74
75
|
if (module === undefined)
|
|
@@ -109,8 +110,6 @@ const createTransformationEvidence = (input, instrumentation) => createEvidence(
|
|
|
109
110
|
],
|
|
110
111
|
});
|
|
111
112
|
const createCharacterizationEvidence = (prepared, replay) => {
|
|
112
|
-
if (replay.phase !== "execute" || replay.evidence === null)
|
|
113
|
-
throw new AnalysisProtocolError("Node characterization execution omitted replay Evidence");
|
|
114
113
|
return createEvidence({
|
|
115
114
|
path: prepared.plan.artifact.path,
|
|
116
115
|
sha256: prepared.plan.artifact.sha256,
|
|
@@ -264,6 +264,11 @@ export const resolveProcessResult = (capture, executionFailure, cleanupFailure)
|
|
|
264
264
|
return capture;
|
|
265
265
|
};
|
|
266
266
|
export const prepareProcessCapture = async (scenario, policy, signal, host = systemProcessPreparationHost) => {
|
|
267
|
+
if (policy.status === "disabled")
|
|
268
|
+
throw new ProcessCaptureError("process capture is disabled", {
|
|
269
|
+
userCategory: "permission_required",
|
|
270
|
+
userMessage: processPolicyMessage("process capture is disabled"),
|
|
271
|
+
});
|
|
267
272
|
const decision = authorizeProcessScenario(scenario, policy);
|
|
268
273
|
if (!decision.allowed)
|
|
269
274
|
throw new ProcessCaptureError(decision.reason, {
|
|
@@ -128,7 +128,8 @@ const readJson = async (path) => {
|
|
|
128
128
|
if (bytes.length > MAX_INPUT_BYTES)
|
|
129
129
|
throw new ProcessCliFailure("truncated", "Process input file is too large. Reduce it below 64 MiB, then try again.");
|
|
130
130
|
try {
|
|
131
|
-
|
|
131
|
+
const parsed = JSON.parse(bytes.toString("utf8"));
|
|
132
|
+
return parsed;
|
|
132
133
|
}
|
|
133
134
|
catch {
|
|
134
135
|
throw new ProcessCliFailure("invalid_input", "Process input file is not valid JSON. Repair the file, then try again.");
|
|
@@ -184,13 +184,18 @@ const completeCapture = async (options) => {
|
|
|
184
184
|
}
|
|
185
185
|
throw processCaptureCancelled();
|
|
186
186
|
}
|
|
187
|
+
const settlement = await observeSettlement(options.runId, selectCapturedProcessGroupIds(runtime.terminal.pid, options.samples), scenario.settle_ms, options.recordEvent);
|
|
188
|
+
runtime.checkpoints.trigger("settled");
|
|
189
|
+
const samplingPartial = (await runtime.stopSampler()).partial;
|
|
187
190
|
if (runtime.reactive !== undefined) {
|
|
191
|
+
// Process, filesystem, shim, and protocol observations can be recorded
|
|
192
|
+
// during settlement after the PTY exits. Make target loss terminal only
|
|
193
|
+
// after that journal has drained, otherwise the coordinator's control
|
|
194
|
+
// priority discards a valid multi-source completion predicate.
|
|
195
|
+
await runtime.reactive.coordinator.drain();
|
|
188
196
|
await runtime.reactive.coordinator.submit({ kind: "target_lost" });
|
|
189
197
|
runtime.reactive.unsubscribe();
|
|
190
198
|
}
|
|
191
|
-
const settlement = await observeSettlement(options.runId, selectCapturedProcessGroupIds(runtime.terminal.pid, options.samples), scenario.settle_ms, options.recordEvent);
|
|
192
|
-
runtime.checkpoints.trigger("settled");
|
|
193
|
-
const samplingPartial = (await runtime.stopSampler()).partial;
|
|
194
199
|
assertNotCancelled(options.signal);
|
|
195
200
|
const after = await snapshotRoots(scenario, options.signal);
|
|
196
201
|
const renderedFrames = await runtime.renderer.frames();
|
|
@@ -39,9 +39,10 @@ export const normalizeProcessSamples = (samples, scenario, rootPid) => {
|
|
|
39
39
|
mapping.set(identifier, mapping.size + 1);
|
|
40
40
|
const normalizeCommand = (command) => {
|
|
41
41
|
const normalized = normalizeProcessText(command, scenario, "<no-temporary-root>", rootPid);
|
|
42
|
-
|
|
42
|
+
const withNormalizedPids = scenario.normalization.pids
|
|
43
43
|
? normalizePidTokens(normalized, identifiers)
|
|
44
44
|
: normalized;
|
|
45
|
+
return withNormalizedPids.trim();
|
|
45
46
|
};
|
|
46
47
|
return samples.map((sample) => ({
|
|
47
48
|
at_ms: normalizeProcessElapsedTime(sample.at_ms, scenario.normalization.time_bucket_ms),
|
|
@@ -173,7 +173,7 @@ export const importReferenceSource = async (options) => {
|
|
|
173
173
|
try {
|
|
174
174
|
return ok(createHistoricalSourceGraph(input));
|
|
175
175
|
}
|
|
176
|
-
catch (
|
|
177
|
-
return err(failure("parse",
|
|
176
|
+
catch (cause) {
|
|
177
|
+
return err(failure("parse", cause instanceof Error ? cause.message : "Graph failed validation"));
|
|
178
178
|
}
|
|
179
179
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { constants as fsConstants } from "node:fs";
|
|
2
2
|
import { copyFile, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { dirname } from "node:path";
|
|
4
|
+
import canonicalize from "canonicalize";
|
|
4
5
|
import { z } from "zod";
|
|
5
6
|
import writeFileAtomic from "write-file-atomic";
|
|
6
7
|
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
@@ -187,7 +188,11 @@ const objectSchema = z.record(z.string(), z.unknown());
|
|
|
187
188
|
const parseOptionalObject = (value) => value === undefined ? {} : objectSchema.parse(value);
|
|
188
189
|
const parseObject = (text) => objectSchema.parse(JSON.parse(text));
|
|
189
190
|
const isMissing = (cause) => cause instanceof Error && "code" in cause && cause.code === "ENOENT";
|
|
190
|
-
const sameConfiguration = (left, right) =>
|
|
191
|
+
const sameConfiguration = (left, right) => {
|
|
192
|
+
const encodedLeft = canonicalize(left);
|
|
193
|
+
const encodedRight = canonicalize(right);
|
|
194
|
+
return encodedLeft !== undefined && encodedLeft === encodedRight;
|
|
195
|
+
};
|
|
191
196
|
const preserveConfigBackup = async (source, destination) => {
|
|
192
197
|
try {
|
|
193
198
|
await copyFile(source, destination, fsConstants.COPYFILE_EXCL);
|
|
@@ -2,6 +2,7 @@ import { constants } from "node:fs";
|
|
|
2
2
|
import { mkdtemp, rm, symlink, writeFile } from "node:fs/promises";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
+
import { windowsNativeCapabilities, } from "../process/WindowsAuthority.js";
|
|
5
6
|
import { probeProcessCaptureCapability, } from "./ProcessCaptureCapability.js";
|
|
6
7
|
/** Probe named prerequisites without converting missing controls into support. */
|
|
7
8
|
export const probeWindowsCapabilities = async (dependencies = systemDependencies()) => {
|
|
@@ -17,11 +18,11 @@ export const probeWindowsCapabilities = async (dependencies = systemDependencies
|
|
|
17
18
|
no_follow_open: dependencies.probeNoFollowOpen(),
|
|
18
19
|
private_acl: dependencies.probePrivateAcl(),
|
|
19
20
|
unix_domain_socket: dependencies.probeUnixDomainSocket(),
|
|
20
|
-
pty:
|
|
21
|
-
available:
|
|
22
|
-
|
|
23
|
-
},
|
|
21
|
+
pty: pty.available
|
|
22
|
+
? { available: true, reason: null }
|
|
23
|
+
: { available: false, reason: pty.reason },
|
|
24
24
|
},
|
|
25
|
+
security: windowsNativeCapabilities(dependencies.platform),
|
|
25
26
|
};
|
|
26
27
|
};
|
|
27
28
|
const systemDependencies = () => ({
|
|
@@ -8,6 +8,7 @@ import { ArtifactReaderFailure, } from "./ArtifactReader.js";
|
|
|
8
8
|
import { ARTIFACT_GRAPH_PROVIDER } from "../application/InvestigationProviders.js";
|
|
9
9
|
import { createEvidence } from "../domain/evidence.js";
|
|
10
10
|
import { createArtifactInspection } from "../domain/artifactInspection.js";
|
|
11
|
+
import { resolveArtifactIntegrityPolicy, resolveNativeMountPolicy, } from "../application/ArtifactInventory/policy.js";
|
|
11
12
|
const IDENTITY = Object.freeze(ARTIFACT_GRAPH_PROVIDER);
|
|
12
13
|
/** Read-only inventory and exclusively owned extraction provider. */
|
|
13
14
|
export class ArtifactProvider {
|
|
@@ -180,16 +181,13 @@ class ArtifactClient {
|
|
|
180
181
|
edgeLimit: parsed.edge_limit,
|
|
181
182
|
}, {
|
|
182
183
|
...(options?.signal === undefined ? {} : { signal: options.signal }),
|
|
183
|
-
nativeMount:
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
enabled: this.integrityContinueEnabled,
|
|
191
|
-
maxMismatches: parsed.max_integrity_mismatches,
|
|
192
|
-
},
|
|
184
|
+
nativeMount: resolveNativeMountPolicy(parsed.native_mount_approved === true, this.nativeMountEnabled),
|
|
185
|
+
integrity: resolveArtifactIntegrityPolicy(parsed.integrity_policy === "fail"
|
|
186
|
+
? { mode: "fail" }
|
|
187
|
+
: {
|
|
188
|
+
mode: parsed.integrity_policy,
|
|
189
|
+
maxMismatches: parsed.max_integrity_mismatches,
|
|
190
|
+
}, this.integrityContinueEnabled),
|
|
193
191
|
});
|
|
194
192
|
}
|
|
195
193
|
}
|
|
@@ -14,9 +14,7 @@ export class SafeOutputTree {
|
|
|
14
14
|
#totalBytes = 0;
|
|
15
15
|
#published = false;
|
|
16
16
|
#cleanup = {
|
|
17
|
-
|
|
18
|
-
verified: false,
|
|
19
|
-
residualPaths: [],
|
|
17
|
+
status: "not-required",
|
|
20
18
|
};
|
|
21
19
|
constructor(outputRoot, stagingRoot, limits) {
|
|
22
20
|
this.#outputRoot = outputRoot;
|
|
@@ -126,11 +124,12 @@ export class SafeOutputTree {
|
|
|
126
124
|
return structuredClone(this.#cleanup);
|
|
127
125
|
await rm(this.#stagingRoot, { recursive: true, force: true });
|
|
128
126
|
const absent = await isAbsent(this.#stagingRoot);
|
|
129
|
-
this.#cleanup =
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
127
|
+
this.#cleanup = absent
|
|
128
|
+
? { status: "complete", residualPaths: [] }
|
|
129
|
+
: {
|
|
130
|
+
status: "incomplete",
|
|
131
|
+
residualPaths: [basename(this.#stagingRoot)],
|
|
132
|
+
};
|
|
134
133
|
if (!absent)
|
|
135
134
|
throw new ArtifactReaderFailure("integrity", "Extraction output cleanup could not be verified");
|
|
136
135
|
return structuredClone(this.#cleanup);
|
|
@@ -19,13 +19,16 @@ export const captureStorageFingerprints = async ({ context, origin, local, sessi
|
|
|
19
19
|
await addIndexedDb(run, origin, indexedDbNames);
|
|
20
20
|
await addCaches(run, caches);
|
|
21
21
|
state.items.sort((left, right) => compareCodePoints(`${left.scope}:${left.identity_sha256}`, `${right.scope}:${right.identity_sha256}`));
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
const complete = state.complete &&
|
|
23
|
+
!state.truncated &&
|
|
24
|
+
state.items.every(({ complete }) => complete);
|
|
25
|
+
return complete
|
|
26
|
+
? { items: state.items, complete: true, truncated: false }
|
|
27
|
+
: {
|
|
28
|
+
items: state.items,
|
|
29
|
+
complete: false,
|
|
30
|
+
truncated: state.truncated,
|
|
31
|
+
};
|
|
29
32
|
};
|
|
30
33
|
const addStorageItems = (state, scope, items) => {
|
|
31
34
|
for (const item of items)
|
|
@@ -44,7 +44,9 @@ export const inspectCdpElectronPage = async (context) => {
|
|
|
44
44
|
};
|
|
45
45
|
const runElectronInspection = async (context, state, startedAt) => {
|
|
46
46
|
const frames = await authorizeInitialFrames(context, state);
|
|
47
|
-
const main = frames
|
|
47
|
+
const main = frames.at(0);
|
|
48
|
+
if (main === undefined)
|
|
49
|
+
throw new BrowserObservationError("inspect_web_page", "target_not_allowed");
|
|
48
50
|
state.mainFrameId = main.frame_id;
|
|
49
51
|
await enableElectronCapture(context);
|
|
50
52
|
await delayWithCancellation(context.input.observation_ms, "inspect_electron_page", context.signal);
|
|
@@ -36,3 +36,8 @@ export const authorizeRuntimeLocation = async (value, roots, origins) => {
|
|
|
36
36
|
},
|
|
37
37
|
};
|
|
38
38
|
};
|
|
39
|
+
/** Authorize a target whose Node Inspector URL omits its main entry path. */
|
|
40
|
+
export const authorizeRuntimeTargetLocation = async (value, targetType, roots, origins) =>
|
|
41
|
+
// A bare file:// URL contains no source identity. Do not attach to the
|
|
42
|
+
// first configured root merely because the target happens to be Node.
|
|
43
|
+
authorizeRuntimeLocation(value, roots, origins);
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { BrowserObservationError } from "../domain/errors.js";
|
|
3
|
+
import { redactSensitiveText } from "./SensitiveTextCapture.js";
|
|
4
|
+
const OPERATION = "capture_electron_scenario";
|
|
5
|
+
const hookEventSchema = z.strictObject({
|
|
6
|
+
sequence: z.number().int().min(1),
|
|
7
|
+
correlation_id: z.string().max(256).nullable(),
|
|
8
|
+
kind: z.enum([
|
|
9
|
+
"main-handler-invocation",
|
|
10
|
+
"main-event-invocation",
|
|
11
|
+
"utility-process-fork",
|
|
12
|
+
"utility-process-message",
|
|
13
|
+
"ipc-main-to-renderer",
|
|
14
|
+
"ipc-utility-to-main",
|
|
15
|
+
"ipc-renderer-send",
|
|
16
|
+
"ipc-renderer-invoke",
|
|
17
|
+
"ipc-renderer-post-message",
|
|
18
|
+
"app-lifecycle",
|
|
19
|
+
"window-lifecycle",
|
|
20
|
+
"web-contents-lifecycle",
|
|
21
|
+
"navigation",
|
|
22
|
+
"shell-attempt",
|
|
23
|
+
"process-lifecycle",
|
|
24
|
+
"permission",
|
|
25
|
+
"popup-attempt",
|
|
26
|
+
"download",
|
|
27
|
+
"protocol",
|
|
28
|
+
"preload",
|
|
29
|
+
"native-addon",
|
|
30
|
+
"updater",
|
|
31
|
+
"error",
|
|
32
|
+
]),
|
|
33
|
+
event: z.string().max(128).nullable(),
|
|
34
|
+
phase: z.enum(["attempted", "completed", "blocked", "failed", "observed"]),
|
|
35
|
+
channel: z.string().max(1_024).nullable(),
|
|
36
|
+
channel_truncated: z.boolean().default(false),
|
|
37
|
+
direction: z
|
|
38
|
+
.enum([
|
|
39
|
+
"renderer-to-main",
|
|
40
|
+
"main-to-renderer",
|
|
41
|
+
"main-to-utility",
|
|
42
|
+
"utility-to-main",
|
|
43
|
+
])
|
|
44
|
+
.nullable(),
|
|
45
|
+
sender: z.string().max(256).nullable(),
|
|
46
|
+
receiver: z.string().max(256).nullable(),
|
|
47
|
+
frame: z.string().max(256).nullable(),
|
|
48
|
+
target: z.string().max(256).nullable(),
|
|
49
|
+
argument_shapes: z.array(z.string().max(64)).max(32),
|
|
50
|
+
argument_shapes_truncated: z.boolean().default(false),
|
|
51
|
+
result_shape: z.string().max(64).nullable(),
|
|
52
|
+
process_type: z.string().max(128).nullable(),
|
|
53
|
+
source: z.string().max(64),
|
|
54
|
+
capture_method: z.enum(["api-wrapper", "event-emitter", "process-hook"]),
|
|
55
|
+
artifact_path: z.string().max(16_384).nullable(),
|
|
56
|
+
artifact_sha256: z
|
|
57
|
+
.string()
|
|
58
|
+
.regex(/^[a-f0-9]{64}$/u)
|
|
59
|
+
.nullable(),
|
|
60
|
+
error: z.boolean(),
|
|
61
|
+
});
|
|
62
|
+
const hookSnapshotSchema = z.strictObject({
|
|
63
|
+
events: z.array(hookEventSchema).max(20_000),
|
|
64
|
+
observed: z.number().int().min(0),
|
|
65
|
+
observed_ipc: z.number().int().min(0),
|
|
66
|
+
observed_runtime: z.number().int().min(0),
|
|
67
|
+
truncated: z.boolean(),
|
|
68
|
+
hook_error: z.boolean(),
|
|
69
|
+
});
|
|
70
|
+
const metricSchema = z.strictObject({
|
|
71
|
+
pid: z.number().int().min(1),
|
|
72
|
+
type: z.string().min(1).max(128),
|
|
73
|
+
name: z.string().max(256).nullable(),
|
|
74
|
+
service_name: z.string().max(256).nullable(),
|
|
75
|
+
});
|
|
76
|
+
const windowMetadataSchema = z.strictObject({
|
|
77
|
+
window_id: z.string().max(256),
|
|
78
|
+
web_contents_id: z.string().max(256),
|
|
79
|
+
visible: z.boolean().nullable(),
|
|
80
|
+
destroyed: z.boolean(),
|
|
81
|
+
});
|
|
82
|
+
/** Run bounded, explicit actions against provider-owned Electron windows. */
|
|
83
|
+
export const runElectronActions = async (application, input, options, deadline) => {
|
|
84
|
+
const actions = [];
|
|
85
|
+
for (const action of input.actions) {
|
|
86
|
+
if (options.signal?.aborted === true)
|
|
87
|
+
throw new BrowserObservationError(OPERATION, "cancelled");
|
|
88
|
+
const actionStartedAt = Date.now();
|
|
89
|
+
const windowIndex = actionWindowIndex(action);
|
|
90
|
+
let selectedWindow;
|
|
91
|
+
try {
|
|
92
|
+
const remaining = deadline - Date.now();
|
|
93
|
+
if (remaining <= 0)
|
|
94
|
+
throw new BrowserObservationError(OPERATION, "timeout");
|
|
95
|
+
selectedWindow =
|
|
96
|
+
windowIndex === null
|
|
97
|
+
? undefined
|
|
98
|
+
: await waitForWindow({
|
|
99
|
+
application,
|
|
100
|
+
windowIndex,
|
|
101
|
+
timeout: input.limits.action_timeout_ms,
|
|
102
|
+
signal: options.signal,
|
|
103
|
+
deadline,
|
|
104
|
+
});
|
|
105
|
+
if ((action.kind === "click" ||
|
|
106
|
+
action.kind === "renderer-reload" ||
|
|
107
|
+
action.kind === "renderer-crash") &&
|
|
108
|
+
selectedWindow === undefined)
|
|
109
|
+
throw new BrowserObservationError(OPERATION, "window_not_found");
|
|
110
|
+
await runAction({
|
|
111
|
+
application,
|
|
112
|
+
action,
|
|
113
|
+
selectedWindow,
|
|
114
|
+
remaining,
|
|
115
|
+
actionTimeoutMs: input.limits.action_timeout_ms,
|
|
116
|
+
options,
|
|
117
|
+
deadline,
|
|
118
|
+
});
|
|
119
|
+
actions.push({
|
|
120
|
+
step_id: action.step_id,
|
|
121
|
+
kind: action.kind,
|
|
122
|
+
window_index: windowIndex,
|
|
123
|
+
target: selectedWindow === undefined ? null : `window:${String(windowIndex)}`,
|
|
124
|
+
status: "completed",
|
|
125
|
+
elapsed_ms: Date.now() - actionStartedAt,
|
|
126
|
+
error: null,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
catch (cause) {
|
|
130
|
+
actions.push({
|
|
131
|
+
step_id: action.step_id,
|
|
132
|
+
kind: action.kind,
|
|
133
|
+
window_index: windowIndex,
|
|
134
|
+
target: selectedWindow === undefined ? null : `window:${String(windowIndex)}`,
|
|
135
|
+
status: options.signal?.aborted ? "cancelled" : "failed",
|
|
136
|
+
elapsed_ms: Date.now() - actionStartedAt,
|
|
137
|
+
error: safeActionErrorMessage(cause, action),
|
|
138
|
+
});
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return actions;
|
|
143
|
+
};
|
|
144
|
+
const runAction = async ({ application, action, selectedWindow, remaining, actionTimeoutMs, options, deadline, }) => {
|
|
145
|
+
switch (action.kind) {
|
|
146
|
+
case "click":
|
|
147
|
+
if (selectedWindow === undefined)
|
|
148
|
+
throw new BrowserObservationError(OPERATION, "window_not_found");
|
|
149
|
+
await runWithExecutionLimits(selectedWindow.locator(action.selector).click({
|
|
150
|
+
timeout: Math.min(actionTimeoutMs, remaining),
|
|
151
|
+
}), options.signal, deadline);
|
|
152
|
+
return;
|
|
153
|
+
case "wait": {
|
|
154
|
+
const page = selectedWindow ??
|
|
155
|
+
(await application.firstWindow({
|
|
156
|
+
timeout: Math.min(actionTimeoutMs, remaining),
|
|
157
|
+
}));
|
|
158
|
+
await runWithExecutionLimits(page.waitForTimeout(Math.min(action.duration_ms, remaining)), options.signal, deadline);
|
|
159
|
+
if (action.duration_ms > remaining)
|
|
160
|
+
throw new BrowserObservationError(OPERATION, "timeout");
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
case "renderer-reload":
|
|
164
|
+
if (selectedWindow === undefined)
|
|
165
|
+
throw new BrowserObservationError(OPERATION, "window_not_found");
|
|
166
|
+
await runWithExecutionLimits(selectedWindow.reload({
|
|
167
|
+
timeout: Math.min(actionTimeoutMs, remaining),
|
|
168
|
+
}), options.signal, deadline);
|
|
169
|
+
return;
|
|
170
|
+
case "renderer-crash": {
|
|
171
|
+
const crashed = await runWithExecutionLimits(application.evaluate(({ BrowserWindow }, index) => {
|
|
172
|
+
const candidate = BrowserWindow.getAllWindows()[index];
|
|
173
|
+
if (candidate === undefined || candidate.isDestroyed())
|
|
174
|
+
return false;
|
|
175
|
+
candidate.webContents.forcefullyCrashRenderer();
|
|
176
|
+
return true;
|
|
177
|
+
}, action.window_index), options.signal, deadline);
|
|
178
|
+
if (!crashed)
|
|
179
|
+
throw new BrowserObservationError(OPERATION, "window_not_found");
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
case "deep-link":
|
|
183
|
+
await runWithExecutionLimits(emitDeepLink(application, action), options.signal, deadline);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
const actionWindowIndex = (action) => {
|
|
188
|
+
switch (action.kind) {
|
|
189
|
+
case "click":
|
|
190
|
+
case "renderer-reload":
|
|
191
|
+
case "renderer-crash":
|
|
192
|
+
return action.window_index;
|
|
193
|
+
case "wait":
|
|
194
|
+
return action.window_index ?? null;
|
|
195
|
+
case "deep-link":
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
const waitForWindow = async ({ application, windowIndex, timeout, signal, deadline, }) => {
|
|
200
|
+
const stopAt = Math.min(deadline, Date.now() + timeout);
|
|
201
|
+
while (Date.now() < stopAt) {
|
|
202
|
+
if (signal?.aborted === true)
|
|
203
|
+
throw new BrowserObservationError(OPERATION, "cancelled");
|
|
204
|
+
const window = application.windows()[windowIndex];
|
|
205
|
+
if (window !== undefined)
|
|
206
|
+
return window;
|
|
207
|
+
await runWithExecutionLimits(new Promise((resolve) => setTimeout(resolve, 25)), signal, stopAt);
|
|
208
|
+
}
|
|
209
|
+
throw new BrowserObservationError(OPERATION, "window_not_found");
|
|
210
|
+
};
|
|
211
|
+
const emitDeepLink = (application, action) => application.evaluate(({ app }, payload) => {
|
|
212
|
+
if (payload.delivery === "open-url")
|
|
213
|
+
return app.emit("open-url", { preventDefault: () => undefined }, payload.url);
|
|
214
|
+
return app.emit("second-instance", {}, [payload.url], "", {});
|
|
215
|
+
}, { delivery: action.delivery, url: action.url });
|
|
216
|
+
/** Read the current Electron windows, metrics, and bounded hook snapshot. */
|
|
217
|
+
// oxlint-disable-next-line max-lines-per-function -- one state read must share one capture boundary.
|
|
218
|
+
export const readApplicationState = async (application, maxWindows) => {
|
|
219
|
+
const pages = application.windows();
|
|
220
|
+
const pageSnapshotsPromise = Promise.all(pages.slice(0, maxWindows).map(async (page, index) => ({
|
|
221
|
+
index,
|
|
222
|
+
url: page.url().slice(0, 65_536),
|
|
223
|
+
title: (await page.title()).slice(0, 16_384),
|
|
224
|
+
})));
|
|
225
|
+
const windowMetadataPromise = application
|
|
226
|
+
.evaluate(({ BrowserWindow }) => BrowserWindow.getAllWindows().map((window) => ({
|
|
227
|
+
window_id: `window:${String(window.id)}`,
|
|
228
|
+
web_contents_id: `webContents:${String(window.webContents.id)}`,
|
|
229
|
+
visible: window.isDestroyed() ? null : window.isVisible(),
|
|
230
|
+
destroyed: window.isDestroyed(),
|
|
231
|
+
})))
|
|
232
|
+
.then((value) => z.array(windowMetadataSchema).parse(value));
|
|
233
|
+
const processMetricsPromise = application
|
|
234
|
+
.evaluate(({ app }) => app
|
|
235
|
+
.getAppMetrics()
|
|
236
|
+
.map((metric) => ({
|
|
237
|
+
pid: metric.pid,
|
|
238
|
+
type: metric.type,
|
|
239
|
+
name: metric.name ?? null,
|
|
240
|
+
service_name: metric.serviceName ?? null,
|
|
241
|
+
})))
|
|
242
|
+
.then((value) => z.array(metricSchema).parse(value));
|
|
243
|
+
const electronVersionPromise = application
|
|
244
|
+
.evaluate(() => process.versions.electron)
|
|
245
|
+
.then((value) => z.string().parse(value));
|
|
246
|
+
const hookSnapshotPromise = application
|
|
247
|
+
.evaluate(() => {
|
|
248
|
+
const candidate = Reflect.get(globalThis, "__reaElectronActiveSnapshot");
|
|
249
|
+
return typeof candidate === "function"
|
|
250
|
+
? candidate()
|
|
251
|
+
: {
|
|
252
|
+
events: [],
|
|
253
|
+
observed: 0,
|
|
254
|
+
observed_ipc: 0,
|
|
255
|
+
observed_runtime: 0,
|
|
256
|
+
truncated: false,
|
|
257
|
+
hook_error: true,
|
|
258
|
+
};
|
|
259
|
+
})
|
|
260
|
+
.then((value) => hookSnapshotSchema.parse(value));
|
|
261
|
+
const [pageSnapshots, windowMetadata, processMetrics, electronVersion, hookSnapshot,] = await Promise.all([
|
|
262
|
+
pageSnapshotsPromise,
|
|
263
|
+
windowMetadataPromise,
|
|
264
|
+
processMetricsPromise,
|
|
265
|
+
electronVersionPromise,
|
|
266
|
+
hookSnapshotPromise,
|
|
267
|
+
]);
|
|
268
|
+
return {
|
|
269
|
+
windows: pageSnapshots.map((page) => {
|
|
270
|
+
const metadata = windowMetadata[page.index];
|
|
271
|
+
if (metadata === undefined)
|
|
272
|
+
throw new BrowserObservationError(OPERATION, "window_metadata_missing");
|
|
273
|
+
return {
|
|
274
|
+
window_id: metadata.window_id,
|
|
275
|
+
web_contents_id: metadata.web_contents_id,
|
|
276
|
+
url: page.url,
|
|
277
|
+
title: page.title,
|
|
278
|
+
visible: metadata.visible,
|
|
279
|
+
destroyed: metadata.destroyed,
|
|
280
|
+
};
|
|
281
|
+
}),
|
|
282
|
+
windowsTruncated: pages.length > maxWindows,
|
|
283
|
+
metrics: processMetrics,
|
|
284
|
+
electronVersion,
|
|
285
|
+
hookSnapshot,
|
|
286
|
+
};
|
|
287
|
+
};
|
|
288
|
+
/** Apply a deadline and caller cancellation to one Playwright operation. */
|
|
289
|
+
export const runWithExecutionLimits = async (operation, signal, deadline) => {
|
|
290
|
+
if (signal?.aborted === true)
|
|
291
|
+
throw new BrowserObservationError(OPERATION, "cancelled");
|
|
292
|
+
let timer;
|
|
293
|
+
let abortListener;
|
|
294
|
+
const timeout = new Promise((_, reject) => {
|
|
295
|
+
timer = setTimeout(() => reject(new BrowserObservationError(OPERATION, "timeout")), Math.max(1, deadline - Date.now()));
|
|
296
|
+
});
|
|
297
|
+
const cancellation = signal === undefined
|
|
298
|
+
? timeout
|
|
299
|
+
: Promise.race([
|
|
300
|
+
timeout,
|
|
301
|
+
new Promise((_, reject) => {
|
|
302
|
+
abortListener = () => reject(new BrowserObservationError(OPERATION, "cancelled"));
|
|
303
|
+
signal.addEventListener("abort", abortListener, { once: true });
|
|
304
|
+
}),
|
|
305
|
+
]);
|
|
306
|
+
try {
|
|
307
|
+
return await Promise.race([operation, cancellation]);
|
|
308
|
+
}
|
|
309
|
+
finally {
|
|
310
|
+
if (timer !== undefined)
|
|
311
|
+
clearTimeout(timer);
|
|
312
|
+
if (abortListener !== undefined)
|
|
313
|
+
signal?.removeEventListener("abort", abortListener);
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
const safeActionErrorMessage = (cause, action) => {
|
|
317
|
+
let message = cause instanceof Error ? cause.message : "Electron action failed";
|
|
318
|
+
const inputValues = Object.values(action)
|
|
319
|
+
.filter((value) => typeof value === "string" && value.length > 0)
|
|
320
|
+
.sort((left, right) => right.length - left.length);
|
|
321
|
+
for (const value of inputValues)
|
|
322
|
+
message = message.split(value).join("<redacted-input>");
|
|
323
|
+
return redactSensitiveText(message).slice(0, 1_024);
|
|
324
|
+
};
|