rea-agents 1.5.0 → 1.7.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 +144 -58
- package/bridge/ghidra/ReaGhidraBridge.java +2075 -0
- package/bridge/hopper_bridge.py +13 -3
- package/dist/application/AbortablePromise.js +25 -0
- package/dist/application/AnalysisProvider.js +3 -0
- package/dist/application/AnalysisProviderEvaluation.js +60 -0
- package/dist/application/AnalysisProviderRegistry.js +228 -0
- package/dist/application/AnalysisSnapshotCache.js +41 -21
- package/dist/application/AnalysisSnapshotFiles.js +5 -6
- package/dist/application/BinarySession.js +229 -102
- package/dist/application/BrowserEvidence.js +87 -15
- package/dist/application/BrowserObservationService.js +65 -1
- package/dist/application/CapabilityInventory.js +7 -0
- package/dist/application/ClientConfigPath.js +33 -0
- package/dist/application/CompositeProvider.js +20 -8
- package/dist/application/CrossVersionInventory.js +16 -3
- package/dist/application/CrossVersionInvestigation.js +17 -62
- package/dist/application/CrossVersionInvestigationEvidence.js +64 -0
- package/dist/application/CrossVersionInvestigationReplay.js +170 -0
- package/dist/application/DirectAnalysis.js +49 -35
- package/dist/application/Doctor.js +70 -13
- package/dist/application/ElectronEvidence.js +35 -0
- package/dist/application/ElectronObservationPort.js +1 -0
- package/dist/application/ElectronObservationService.js +46 -0
- package/dist/application/EnhancedTools.js +1 -1
- package/dist/application/EvidenceLedger.js +5 -2
- package/dist/application/InvestigationProviders.js +11 -0
- package/dist/application/JavaScriptArtifactAnalysis.js +299 -0
- package/dist/application/JavaScriptArtifactAnalysisTypes.js +1 -0
- package/dist/application/JavaScriptArtifactFiles.js +231 -0
- package/dist/application/JavaScriptArtifactGraphAccumulator.js +54 -0
- package/dist/application/JavaScriptArtifactGraphBuilder.js +101 -0
- package/dist/application/JavaScriptArtifactGraphContext.js +205 -0
- package/dist/application/JavaScriptArtifactGraphDocuments.js +108 -0
- package/dist/application/JavaScriptArtifactGraphEvidence.js +89 -0
- package/dist/application/JavaScriptArtifactGraphFindings.js +271 -0
- package/dist/application/JavaScriptArtifactGraphStructure.js +409 -0
- package/dist/application/JavaScriptArtifactReconstruction.js +74 -0
- package/dist/application/JavaScriptArtifactReconstructionInput.js +99 -0
- package/dist/application/MacHopper.js +20 -4
- package/dist/application/PermissionAuthority.js +30 -0
- package/dist/application/ProcessCaptureLifecycle.js +2 -6
- package/dist/application/ProcessHarness.js +13 -7
- package/dist/application/ProcessSampling.js +59 -8
- package/dist/application/ProjectPermissionStore.js +1 -0
- package/dist/application/ReferenceSourceImport.js +13 -16
- package/dist/application/SessionProviderRouter.js +219 -0
- package/dist/application/Setup.js +153 -70
- package/dist/application/SetupClients.js +8 -5
- package/dist/application/SetupInstallFailure.js +24 -0
- package/dist/application/SetupPlan.js +19 -8
- package/dist/application/SetupSkill.js +20 -3
- package/dist/application/SupportedClients.js +40 -16
- package/dist/application/Uninstall.js +20 -7
- package/dist/application/Upgrade.js +15 -4
- package/dist/application/runtime.js +9 -6
- package/dist/artifacts/AsarArtifactReader.js +23 -3
- package/dist/browser/CdpBrowserProvider.js +158 -53
- package/dist/browser/CdpCaptureCompleteness.js +90 -0
- package/dist/browser/CdpCaptureDocuments.js +180 -21
- package/dist/browser/CdpCaptureEvents.js +342 -43
- package/dist/browser/CdpCaptureValues.js +5 -7
- package/dist/browser/CdpConnection.js +47 -24
- package/dist/browser/CdpElectronInspection.js +330 -0
- package/dist/browser/CdpElectronProvider.js +136 -0
- package/dist/browser/CdpEndpoint.js +77 -5
- package/dist/browser/CdpObservationSession.js +301 -0
- package/dist/browser/CdpPageCapture.js +159 -56
- package/dist/browser/CdpSafeMetadata.js +203 -0
- package/dist/browser/CdpScreenshot.js +101 -0
- package/dist/browser/CdpTargetSession.js +47 -0
- package/dist/browser/CdpWebMcpDiscovery.js +242 -0
- package/dist/browser/ElectronFileScope.js +50 -0
- package/dist/browser/PngVisualDiff.js +177 -0
- package/dist/browser/SensitiveTextCapture.js +25 -0
- package/dist/browser/WebSourceMapFetcher.js +357 -0
- package/dist/catalogIdentity.js +2 -33
- package/dist/cli.js +62 -34
- package/dist/cliBrowserAdvancedCommands.js +170 -0
- package/dist/cliBrowserCommands.js +180 -3
- package/dist/cliCommandNames.js +43 -0
- package/dist/cliElectronCommands.js +115 -0
- package/dist/cliEvidenceCommands.js +4 -3
- package/dist/cliInvestigationCommands.js +17 -9
- package/dist/cliPolicyCommands.js +49 -3
- package/dist/cliProcessCommands.js +3 -2
- package/dist/config.js +58 -7
- package/dist/contracts/browserToolContracts.js +304 -3
- package/dist/contracts/electronToolContracts.js +73 -0
- package/dist/contracts/enhancedInputs.js +2 -2
- package/dist/contracts/providerSelection.js +28 -0
- package/dist/contracts/sessionLifecycleInputs.js +2 -0
- package/dist/contracts/toolContracts.js +24 -20
- package/dist/contracts/toolOutputSchemas.js +117 -22
- package/dist/doctorRuntime.js +6 -0
- package/dist/domain/analysisProfile.js +78 -0
- package/dist/domain/analysisSnapshot.js +92 -46
- package/dist/domain/artifactGraph.js +1 -0
- package/dist/domain/binaryTarget.js +4 -29
- package/dist/domain/browserCompleteness.js +101 -0
- package/dist/domain/browserObservation.js +240 -14
- package/dist/domain/browserSession.js +63 -0
- package/dist/domain/electronObservation.js +162 -0
- package/dist/domain/errors.js +81 -6
- package/dist/domain/evidence.js +44 -5
- package/dist/domain/evidenceBundle.js +9 -7
- package/dist/domain/functionComparison.js +9 -3
- package/dist/domain/functionComparisonNormalization.js +37 -0
- package/dist/domain/functionDossierEvidence.js +4 -1
- package/dist/domain/hopperValues.js +66 -9
- package/dist/domain/investigationWorkspace.js +6 -1
- package/dist/domain/javascriptApplicationEvidenceSchemas.js +361 -0
- package/dist/domain/javascriptApplicationGraph.js +281 -0
- package/dist/domain/javascriptApplicationGraphSchemas.js +132 -0
- package/dist/domain/javascriptAstFingerprint.js +143 -0
- package/dist/domain/javascriptStaticAnalysis.js +358 -0
- package/dist/domain/javascriptStaticAnalysisHelpers.js +264 -0
- package/dist/domain/javascriptStaticAnalysisState.js +17 -0
- package/dist/domain/javascriptStaticAnalysisTypes.js +1 -0
- package/dist/domain/jsonShape.js +125 -0
- package/dist/domain/referenceSourceGraph.js +3 -1
- package/dist/domain/webBundleAnalysis.js +195 -0
- package/dist/domain/webBundleAnalyzer.js +423 -0
- package/dist/domain/webCaptureDiff.js +184 -0
- package/dist/domain/webContentArtifact.js +47 -0
- package/dist/domain/webInventory.js +61 -0
- package/dist/domain/webMcpDiscovery.js +68 -0
- package/dist/domain/webScreenshot.js +114 -0
- package/dist/generatedPackageMetadata.js +2 -2
- package/dist/ghidra/GhidraAnalysisProfile.js +37 -0
- package/dist/ghidra/GhidraClient.js +400 -0
- package/dist/ghidra/GhidraClientTypes.js +1 -0
- package/dist/ghidra/GhidraDefaults.js +20 -0
- package/dist/ghidra/GhidraDiagnostics.js +43 -0
- package/dist/ghidra/GhidraDoctor.js +59 -0
- package/dist/ghidra/GhidraFunctionValues.js +264 -0
- package/dist/ghidra/GhidraInstallation.js +239 -0
- package/dist/ghidra/GhidraInventoryValues.js +217 -0
- package/dist/ghidra/GhidraLauncher.js +139 -0
- package/dist/ghidra/GhidraProvider.js +360 -0
- package/dist/ghidra/GhidraRequestQueue.js +98 -0
- package/dist/ghidra/GhidraResponseBuffer.js +31 -0
- package/dist/ghidra/GhidraResponseRouter.js +24 -0
- package/dist/ghidra/GhidraSessionError.js +39 -0
- package/dist/ghidra/GhidraSessionValues.js +62 -0
- package/dist/ghidra/GhidraSocketConnection.js +46 -0
- package/dist/ghidra/protocol.js +69 -0
- package/dist/hopper/BridgeLauncher.js +21 -57
- package/dist/hopper/HopperAnalysisProfile.js +106 -0
- package/dist/hopper/HopperClient.js +197 -165
- package/dist/hopper/HopperProvider.js +105 -8
- package/dist/hopper/HopperSessionValues.js +27 -0
- package/dist/hopper/HopperSocketConnection.js +31 -0
- package/dist/main.js +36 -31
- package/dist/native/NativeMacOSProvider.js +6 -3
- package/dist/native/NativeMachoInspection.js +21 -6
- package/dist/process/PendingOperations.js +59 -0
- package/dist/process/PrivateRuntimeRoot.js +33 -0
- package/dist/{application → process}/ProcessOwnership.js +40 -20
- package/dist/process/ProviderDeadline.js +95 -0
- package/dist/process/ProviderProcess.js +275 -0
- package/dist/server/createServer.js +8 -0
- package/dist/server/promptCompletion.js +9 -3
- package/dist/server/registerBrowserTools.js +61 -2
- package/dist/server/registerElectronTools.js +36 -0
- package/dist/server/registerEnhancedTools.js +9 -6
- package/dist/server/registerInvestigationTools.js +11 -12
- package/dist/server/registerOfficialTools.js +3 -0
- package/dist/server/registerSessionTools.js +7 -1
- package/package.json +12 -6
- package/scripts/rea.mjs +37 -3
- package/skills/rea-analysis/SKILL.md +19 -9
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { parseBinaryTarget } from "../domain/binaryTarget.js";
|
|
2
|
-
import {
|
|
2
|
+
import { analysisProfilesEqual, } from "../domain/analysisProfile.js";
|
|
3
|
+
import { AnalysisCapabilityUnavailableError, EvidenceIntegrityError, EvidenceLimitError, AnalysisCancelledError, NoBinaryOpenError, ProviderAdapterError, } from "../domain/errors.js";
|
|
3
4
|
import { err, ok } from "../domain/result.js";
|
|
4
5
|
import { createEvidence } from "../domain/evidence.js";
|
|
5
6
|
import { evidenceBundleForTarget } from "../domain/evidenceBundle.js";
|
|
@@ -7,27 +8,25 @@ import { EvidenceLedger } from "./EvidenceLedger.js";
|
|
|
7
8
|
import { UnknownRegistryError } from "../domain/errors.js";
|
|
8
9
|
import { enhancedToolNameSchema } from "../contracts/enhancedInputs.js";
|
|
9
10
|
import { OFFICIAL_TOOL_CONTRACTS } from "../contracts/toolContracts.js";
|
|
10
|
-
import { snapshotMatchesTarget } from "../domain/analysisSnapshot.js";
|
|
11
|
+
import { snapshotMatchesBinding, snapshotMatchesTarget, snapshotTarget, } from "../domain/analysisSnapshot.js";
|
|
11
12
|
import { AnalysisSnapshotCache, isSnapshotCacheable, } from "./AnalysisSnapshotCache.js";
|
|
12
13
|
import { UNKNOWN_REGISTRY_PROVIDER, unknownEvidenceLinks, unknownMutationEvidence, } from "./UnknownEvidence.js";
|
|
13
14
|
import { parseInvestigationWorkspace, } from "../domain/investigationWorkspace.js";
|
|
15
|
+
import { SessionProviderRouter, } from "./SessionProviderRouter.js";
|
|
14
16
|
const OFFICIAL_OPERATIONS = new Set(OFFICIAL_TOOL_CONTRACTS.map(({ name }) => name));
|
|
15
17
|
/**
|
|
16
18
|
* Owns the single active target shared by CLI and MCP adapters.
|
|
17
19
|
*
|
|
18
|
-
* Target transitions are serialized because
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* client whose bridge was already shut down.
|
|
20
|
+
* Target transitions are serialized because switching targets tears down the
|
|
21
|
+
* active provider client. A failed switch recreates the previous target instead
|
|
22
|
+
* of retaining a client whose resources were already shut down.
|
|
22
23
|
*/
|
|
23
24
|
export class BinarySession {
|
|
24
25
|
provider;
|
|
25
26
|
#active;
|
|
26
27
|
#transition = Promise.resolve();
|
|
27
28
|
#calls = new Set();
|
|
28
|
-
#
|
|
29
|
-
#providerIdentity;
|
|
30
|
-
#capabilities;
|
|
29
|
+
#providerRouter;
|
|
31
30
|
#evidence = new EvidenceLedger({
|
|
32
31
|
maxRecords: 10_000,
|
|
33
32
|
maxBytes: 64 * 1024 * 1024,
|
|
@@ -36,46 +35,55 @@ export class BinarySession {
|
|
|
36
35
|
#investigationWorkspaces = new Map();
|
|
37
36
|
#runtimeAvailability = new Map();
|
|
38
37
|
#availabilityListeners = new Set();
|
|
39
|
-
#snapshotsEnabled;
|
|
40
38
|
#snapshotInvalidated = false;
|
|
41
39
|
constructor(provider, options = {}) {
|
|
42
40
|
this.provider = provider;
|
|
43
|
-
this.#
|
|
44
|
-
|
|
45
|
-
typeof provider === "function"
|
|
41
|
+
this.#providerRouter =
|
|
42
|
+
provider instanceof SessionProviderRouter
|
|
46
43
|
? provider
|
|
47
|
-
: (
|
|
48
|
-
this.#providerIdentity =
|
|
49
|
-
typeof provider === "function"
|
|
50
|
-
? { id: "unidentified", name: "Unidentified provider", version: null }
|
|
51
|
-
: provider.identity();
|
|
52
|
-
this.#capabilities =
|
|
53
|
-
typeof provider === "function"
|
|
54
|
-
? undefined
|
|
55
|
-
: new Map(provider
|
|
56
|
-
.capabilities()
|
|
57
|
-
.map((descriptor) => [descriptor.operation, descriptor]));
|
|
44
|
+
: SessionProviderRouter.legacy(provider, options);
|
|
58
45
|
}
|
|
59
46
|
/** Identify the provider producing evidence for this session. */
|
|
60
47
|
providerIdentity(operation) {
|
|
48
|
+
const route = this.#currentRoute();
|
|
49
|
+
let selected = route.identity;
|
|
61
50
|
if (operation !== undefined) {
|
|
62
|
-
const exact =
|
|
51
|
+
const exact = route.capabilities?.get(operation)?.provider;
|
|
63
52
|
if (exact !== undefined)
|
|
64
|
-
|
|
65
|
-
if (enhancedToolNameSchema.safeParse(operation).success) {
|
|
53
|
+
selected = exact;
|
|
54
|
+
else if (enhancedToolNameSchema.safeParse(operation).success) {
|
|
66
55
|
const providers = new Map();
|
|
67
|
-
for (const descriptor of
|
|
56
|
+
for (const descriptor of route.capabilities?.values() ?? [])
|
|
68
57
|
if (descriptor.available &&
|
|
69
58
|
OFFICIAL_OPERATIONS.has(descriptor.operation))
|
|
70
59
|
providers.set(descriptor.provider.id, descriptor.provider);
|
|
71
60
|
if (providers.size === 1) {
|
|
72
61
|
const provider = providers.values().next().value;
|
|
73
62
|
if (provider !== undefined)
|
|
74
|
-
|
|
63
|
+
selected = provider;
|
|
75
64
|
}
|
|
76
65
|
}
|
|
77
66
|
}
|
|
78
|
-
|
|
67
|
+
const profile = this.#active?.profile;
|
|
68
|
+
return structuredClone(profile !== null &&
|
|
69
|
+
profile !== undefined &&
|
|
70
|
+
profile.provider.id === selected.id
|
|
71
|
+
? profile.provider
|
|
72
|
+
: selected);
|
|
73
|
+
}
|
|
74
|
+
/** Return the selected immutable profile, optionally scoped to an operation. */
|
|
75
|
+
analysisProfile(operation) {
|
|
76
|
+
const profile = this.#active?.profile;
|
|
77
|
+
if (profile === null || profile === undefined)
|
|
78
|
+
return undefined;
|
|
79
|
+
if (operation !== undefined &&
|
|
80
|
+
this.providerIdentity(operation).id !== profile.provider.id)
|
|
81
|
+
return undefined;
|
|
82
|
+
return structuredClone(profile);
|
|
83
|
+
}
|
|
84
|
+
/** Return opaque adapter metadata retained for legacy open_binary output. */
|
|
85
|
+
openCompatibility() {
|
|
86
|
+
return structuredClone(this.#active?.compatibility ?? {});
|
|
79
87
|
}
|
|
80
88
|
/** Observe runtime provider-health changes that affect discovery metadata. */
|
|
81
89
|
onAvailabilityChanged(listener) {
|
|
@@ -104,20 +112,24 @@ export class BinarySession {
|
|
|
104
112
|
}
|
|
105
113
|
/** Export immutable cached calls and session evidence for the active target. */
|
|
106
114
|
exportAnalysisSnapshot() {
|
|
107
|
-
if (!this.#snapshotsEnabled)
|
|
108
|
-
return err(new EvidenceIntegrityError("Analysis snapshots are unavailable with custom Hopper loader arguments"));
|
|
109
115
|
if (this.#snapshotInvalidated)
|
|
110
116
|
return err(new EvidenceIntegrityError("Analysis snapshots are unavailable after analysis metadata mutations"));
|
|
111
117
|
const target = this.#active?.target;
|
|
112
|
-
|
|
118
|
+
const profile = this.#active?.profile ?? undefined;
|
|
119
|
+
if (target !== undefined && profile === undefined)
|
|
120
|
+
return err(new EvidenceIntegrityError("Analysis snapshots require a concrete provider analysis profile"));
|
|
121
|
+
return this.#snapshot.export(target, profile, target === undefined
|
|
113
122
|
? this.#evidence.export()
|
|
114
123
|
: evidenceBundleForTarget(this.#evidence.export(), target.sha256));
|
|
115
124
|
}
|
|
116
125
|
/** Stage validated cached calls for an identical binary and merge its evidence. */
|
|
117
126
|
importAnalysisSnapshot(snapshot) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
127
|
+
const active = this.#active;
|
|
128
|
+
if (active?.profile === null)
|
|
129
|
+
return err(new EvidenceIntegrityError("Analysis snapshot profile_mismatch: the active target has no concrete analysis profile"));
|
|
130
|
+
return this.#snapshot.import(snapshot, active === undefined
|
|
131
|
+
? undefined
|
|
132
|
+
: { target: active.target, profile: active.profile }, (bundle) => this.#evidence.import(bundle));
|
|
121
133
|
}
|
|
122
134
|
/** Retain one validated immutable workspace revision for session resources. */
|
|
123
135
|
retainInvestigationWorkspace(workspace) {
|
|
@@ -187,14 +199,31 @@ export class BinarySession {
|
|
|
187
199
|
const parsed = await parseBinaryTarget(path, process.cwd(), process.arch, options.targetKind);
|
|
188
200
|
if (!parsed.ok)
|
|
189
201
|
return parsed;
|
|
202
|
+
const sameTarget = this.#active?.target.path === parsed.value.path &&
|
|
203
|
+
snapshotMatchesTarget(snapshotTarget(this.#active.target), parsed.value);
|
|
204
|
+
const resolvedRoute = sameTarget &&
|
|
205
|
+
options.providerId === undefined &&
|
|
206
|
+
this.#active !== undefined
|
|
207
|
+
? ok(this.#active.route)
|
|
208
|
+
: await this.#providerRouter.resolve(parsed.value, options.providerId, options.signal);
|
|
209
|
+
if (!resolvedRoute.ok)
|
|
210
|
+
return resolvedRoute;
|
|
211
|
+
const route = resolvedRoute.value;
|
|
212
|
+
const { profile, compatibility } = route;
|
|
190
213
|
if (options.snapshot !== undefined &&
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
214
|
+
(profile === null ||
|
|
215
|
+
!snapshotMatchesBinding(options.snapshot, parsed.value, profile)))
|
|
216
|
+
return err(new EvidenceIntegrityError("Analysis snapshot profile_mismatch: target, provider, or analysis profile does not match the requested binary"));
|
|
217
|
+
if (this.#active === undefined &&
|
|
218
|
+
!this.#snapshot.matches(parsed.value, profile ?? undefined))
|
|
219
|
+
return err(new EvidenceIntegrityError("Analysis snapshot profile_mismatch: staged target, provider, or analysis profile does not match"));
|
|
195
220
|
if (isAborted(options.signal))
|
|
196
221
|
return err(new AnalysisCancelledError("open_binary"));
|
|
197
|
-
|
|
222
|
+
const activeProfile = this.#active?.profile;
|
|
223
|
+
const sameProfile = activeProfile === null || activeProfile === undefined
|
|
224
|
+
? profile === null
|
|
225
|
+
: profile !== null && analysisProfilesEqual(activeProfile, profile);
|
|
226
|
+
if (sameTarget && sameProfile) {
|
|
198
227
|
if (options.snapshot !== undefined) {
|
|
199
228
|
const imported = this.importAnalysisSnapshot(options.snapshot);
|
|
200
229
|
if (!imported.ok)
|
|
@@ -206,7 +235,7 @@ export class BinarySession {
|
|
|
206
235
|
const previous = this.#active;
|
|
207
236
|
this.#active = undefined;
|
|
208
237
|
await previous?.client.close();
|
|
209
|
-
const client =
|
|
238
|
+
const client = route.createClient(parsed.value);
|
|
210
239
|
const started = await client.execute("health", {}, options);
|
|
211
240
|
if (!started.ok) {
|
|
212
241
|
await client.close();
|
|
@@ -218,9 +247,18 @@ export class BinarySession {
|
|
|
218
247
|
await this.#restore(previous);
|
|
219
248
|
return err(new AnalysisCancelledError("open_binary"));
|
|
220
249
|
}
|
|
221
|
-
this.#active = {
|
|
250
|
+
this.#active = {
|
|
251
|
+
target: parsed.value,
|
|
252
|
+
client,
|
|
253
|
+
profile,
|
|
254
|
+
compatibility: structuredClone(compatibility),
|
|
255
|
+
route,
|
|
256
|
+
};
|
|
222
257
|
this.#clearRuntimeAvailability();
|
|
223
|
-
|
|
258
|
+
if (profile === null)
|
|
259
|
+
this.#snapshot.clear();
|
|
260
|
+
else
|
|
261
|
+
this.#snapshot.select(parsed.value, profile);
|
|
224
262
|
if (options.snapshot !== undefined) {
|
|
225
263
|
const imported = this.importAnalysisSnapshot(options.snapshot);
|
|
226
264
|
if (!imported.ok) {
|
|
@@ -251,59 +289,74 @@ export class BinarySession {
|
|
|
251
289
|
/** Describe the active binary session. */
|
|
252
290
|
status() {
|
|
253
291
|
const target = this.#active?.target;
|
|
292
|
+
const route = this.#currentRoute();
|
|
293
|
+
const configuredProvider = this.#providerRouter.configuredIdentity();
|
|
254
294
|
const provider = {
|
|
255
|
-
id:
|
|
256
|
-
name:
|
|
257
|
-
version:
|
|
295
|
+
id: configuredProvider.id,
|
|
296
|
+
name: configuredProvider.name,
|
|
297
|
+
version: configuredProvider.version,
|
|
258
298
|
};
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
providers.set(provider.id, provider);
|
|
262
|
-
else
|
|
263
|
-
for (const descriptor of this.#capabilities.values())
|
|
264
|
-
providers.set(descriptor.provider.id, descriptor.provider);
|
|
265
|
-
const providerList = [...providers.values()]
|
|
266
|
-
.sort((left, right) => left.id.localeCompare(right.id))
|
|
299
|
+
const providerList = this.#providerRouter
|
|
300
|
+
.providerIdentities(route)
|
|
267
301
|
.map(({ id, name, version }) => ({ id, name, version }));
|
|
268
|
-
const capabilities =
|
|
302
|
+
const capabilities = route.capabilities === undefined
|
|
269
303
|
? []
|
|
270
|
-
: [...
|
|
304
|
+
: [...route.capabilities.values()]
|
|
271
305
|
.sort((left, right) => left.operation.localeCompare(right.operation))
|
|
272
306
|
.map((descriptor) => ({
|
|
273
307
|
...descriptor,
|
|
274
308
|
...this.#runtimeAvailability.get(descriptor.operation),
|
|
275
309
|
}))
|
|
276
|
-
.map((descriptor) => (
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
effects: {
|
|
285
|
-
mutates_artifact: descriptor.effects.mutatesArtifact,
|
|
286
|
-
launches_process: descriptor.effects.launchesProcess,
|
|
287
|
-
may_show_ui: descriptor.effects.mayShowUi,
|
|
288
|
-
may_access_network: descriptor.effects.mayAccessNetwork,
|
|
289
|
-
may_write_filesystem: descriptor.effects.mayWriteFilesystem,
|
|
290
|
-
changes_permissions: descriptor.effects.changesPermissions,
|
|
291
|
-
requires_root: descriptor.effects.requiresRoot,
|
|
292
|
-
},
|
|
293
|
-
limits: {
|
|
294
|
-
max_results: descriptor.limits.maxResults,
|
|
295
|
-
max_payload_bytes: descriptor.limits.maxPayloadBytes,
|
|
296
|
-
timeout_ms: descriptor.limits.timeoutMs,
|
|
310
|
+
.map((descriptor) => capabilityStatus(descriptor));
|
|
311
|
+
const analysisProviderBinding = route.binding === null
|
|
312
|
+
? null
|
|
313
|
+
: {
|
|
314
|
+
provider: {
|
|
315
|
+
id: route.binding.identity.id,
|
|
316
|
+
name: route.binding.identity.name,
|
|
317
|
+
version: route.binding.identity.version,
|
|
297
318
|
},
|
|
298
|
-
|
|
299
|
-
|
|
319
|
+
selection_source: route.binding.selectionSource,
|
|
320
|
+
analysis_profile: structuredClone(route.binding.profile),
|
|
321
|
+
};
|
|
322
|
+
const analysisProviderCandidates = this.#providerRouter
|
|
323
|
+
.candidateStatuses(route)
|
|
324
|
+
.map((candidate) => ({
|
|
325
|
+
provider: {
|
|
326
|
+
id: candidate.provider.id,
|
|
327
|
+
name: candidate.provider.name,
|
|
328
|
+
version: candidate.provider.version,
|
|
329
|
+
},
|
|
330
|
+
availability: {
|
|
331
|
+
status: candidate.availability.status,
|
|
332
|
+
code: candidate.availability.code,
|
|
333
|
+
reason: candidate.availability.reason,
|
|
334
|
+
diagnostics: structuredClone(candidate.availability.diagnostics),
|
|
335
|
+
},
|
|
336
|
+
target_support: {
|
|
337
|
+
status: candidate.targetSupport.status,
|
|
338
|
+
code: candidate.targetSupport.code,
|
|
339
|
+
reason: candidate.targetSupport.reason,
|
|
340
|
+
diagnostics: structuredClone(candidate.targetSupport.diagnostics),
|
|
341
|
+
},
|
|
342
|
+
selected: candidate.selected,
|
|
343
|
+
capabilities: candidate.capabilities
|
|
344
|
+
.slice()
|
|
345
|
+
.sort((left, right) => left.operation.localeCompare(right.operation))
|
|
346
|
+
.map((descriptor) => capabilityStatus(descriptor)),
|
|
347
|
+
}));
|
|
348
|
+
const common = {
|
|
349
|
+
provider,
|
|
350
|
+
providers: providerList,
|
|
351
|
+
capabilities,
|
|
352
|
+
analysis_provider_binding: analysisProviderBinding,
|
|
353
|
+
analysis_provider_candidates: analysisProviderCandidates,
|
|
354
|
+
};
|
|
300
355
|
return target === undefined
|
|
301
|
-
? { open: false,
|
|
356
|
+
? { open: false, ...common }
|
|
302
357
|
: {
|
|
303
358
|
open: true,
|
|
304
|
-
|
|
305
|
-
providers: providerList,
|
|
306
|
-
capabilities,
|
|
359
|
+
...common,
|
|
307
360
|
path: target.path,
|
|
308
361
|
sha256: target.sha256,
|
|
309
362
|
format: target.format,
|
|
@@ -311,14 +364,14 @@ export class BinarySession {
|
|
|
311
364
|
architecture: target.architecture ?? null,
|
|
312
365
|
};
|
|
313
366
|
}
|
|
314
|
-
/** Return the immutable artifact identity captured before
|
|
367
|
+
/** Return the immutable artifact identity captured before its provider started. */
|
|
315
368
|
activeTarget() {
|
|
316
369
|
return this.#active === undefined
|
|
317
370
|
? undefined
|
|
318
371
|
: structuredClone(this.#active.target);
|
|
319
372
|
}
|
|
320
373
|
/**
|
|
321
|
-
* Invoke a
|
|
374
|
+
* Invoke a provider operation against the active target.
|
|
322
375
|
* Calls may overlap, but a pending target transition prevents new calls from
|
|
323
376
|
* entering until the transition has settled.
|
|
324
377
|
*/
|
|
@@ -326,15 +379,26 @@ export class BinarySession {
|
|
|
326
379
|
const transitioned = await this.#waitForTransition(name, options?.signal);
|
|
327
380
|
if (!transitioned.ok)
|
|
328
381
|
return transitioned;
|
|
329
|
-
const capability = this.#capabilities?.get(name);
|
|
330
|
-
if (this.#capabilities !== undefined && capability?.available !== true)
|
|
331
|
-
return err(new AnalysisCapabilityUnavailableError(this.#providerIdentity.id, name, capability?.reason ?? "operation is not declared by this provider"));
|
|
332
382
|
const active = this.#active;
|
|
333
383
|
if (active === undefined)
|
|
334
384
|
return err(new NoBinaryOpenError());
|
|
335
|
-
const
|
|
385
|
+
const capability = active.route.capabilities?.get(name);
|
|
386
|
+
if (active.route.capabilities !== undefined &&
|
|
387
|
+
capability?.available !== true) {
|
|
388
|
+
const selectionError = this.#providerRouter.unboundOperationError(name, active.route);
|
|
389
|
+
if (selectionError !== undefined)
|
|
390
|
+
return err(selectionError);
|
|
391
|
+
return err(new AnalysisCapabilityUnavailableError(active.route.binding?.identity.id ?? active.route.identity.id, name, capability?.reason ?? "operation is not declared by this provider"));
|
|
392
|
+
}
|
|
393
|
+
const profile = active.profile !== null &&
|
|
394
|
+
(capability === undefined ||
|
|
395
|
+
capability.provider.id === active.profile.provider.id)
|
|
396
|
+
? active.profile
|
|
397
|
+
: undefined;
|
|
398
|
+
const cacheable = profile !== undefined &&
|
|
399
|
+
isSnapshotCacheable(name, capability, arguments_);
|
|
336
400
|
const cached = cacheable
|
|
337
|
-
? this.#snapshot.lookup(active.target, name, arguments_
|
|
401
|
+
? this.#snapshot.lookup(active.target, profile, name, arguments_)
|
|
338
402
|
: undefined;
|
|
339
403
|
if (cached !== undefined)
|
|
340
404
|
return ok(cached);
|
|
@@ -342,14 +406,21 @@ export class BinarySession {
|
|
|
342
406
|
this.#calls.add(call);
|
|
343
407
|
try {
|
|
344
408
|
const result = await call;
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
409
|
+
const profiled = commitExecutionProfile(name, result, profile);
|
|
410
|
+
this.#observeRuntimeAvailability(name, profiled);
|
|
411
|
+
if (profiled.ok && cacheable)
|
|
412
|
+
this.#snapshot.record({
|
|
413
|
+
target: active.target,
|
|
414
|
+
profile,
|
|
415
|
+
operation: name,
|
|
416
|
+
parameters: arguments_,
|
|
417
|
+
execution: profiled.value,
|
|
418
|
+
});
|
|
419
|
+
else if (profiled.ok && capability?.effects.mutatesArtifact === true) {
|
|
349
420
|
this.#snapshot.clear();
|
|
350
421
|
this.#snapshotInvalidated = true;
|
|
351
422
|
}
|
|
352
|
-
return
|
|
423
|
+
return profiled;
|
|
353
424
|
}
|
|
354
425
|
finally {
|
|
355
426
|
this.#calls.delete(call);
|
|
@@ -372,9 +443,10 @@ export class BinarySession {
|
|
|
372
443
|
"HopperStartError",
|
|
373
444
|
"HopperRemoteError",
|
|
374
445
|
].includes(result.error._tag)) {
|
|
375
|
-
const
|
|
446
|
+
const capabilities = this.#active?.route.capabilities;
|
|
447
|
+
const providerId = capabilities?.get(operation)?.provider.id;
|
|
376
448
|
let changed = false;
|
|
377
|
-
for (const descriptor of
|
|
449
|
+
for (const descriptor of capabilities?.values() ?? [])
|
|
378
450
|
if (providerId !== undefined && descriptor.provider.id === providerId)
|
|
379
451
|
changed =
|
|
380
452
|
this.#setRuntimeAvailability(descriptor.operation, false, "Provider became unavailable during this session.") || changed;
|
|
@@ -401,8 +473,20 @@ export class BinarySession {
|
|
|
401
473
|
this.#emitAvailabilityChanged();
|
|
402
474
|
}
|
|
403
475
|
#emitAvailabilityChanged() {
|
|
404
|
-
for (const listener of this.#availabilityListeners)
|
|
405
|
-
|
|
476
|
+
for (const listener of this.#availabilityListeners) {
|
|
477
|
+
try {
|
|
478
|
+
const notification = listener();
|
|
479
|
+
if (notification !== undefined)
|
|
480
|
+
void notification.catch(() => undefined);
|
|
481
|
+
}
|
|
482
|
+
catch {
|
|
483
|
+
// External observers are best-effort notifications. Contain only the
|
|
484
|
+
// callback failure so state transitions and other listeners continue.
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
#currentRoute() {
|
|
489
|
+
return this.#active?.route ?? this.#providerRouter.initialRoute();
|
|
406
490
|
}
|
|
407
491
|
#serialize(operation) {
|
|
408
492
|
const result = this.#transition.then(operation, operation);
|
|
@@ -415,10 +499,16 @@ export class BinarySession {
|
|
|
415
499
|
async #restore(previous) {
|
|
416
500
|
if (previous === undefined)
|
|
417
501
|
return;
|
|
418
|
-
const client =
|
|
502
|
+
const client = previous.route.createClient(previous.target);
|
|
419
503
|
const started = await client.execute("health", {});
|
|
420
504
|
if (started.ok)
|
|
421
|
-
this.#active = {
|
|
505
|
+
this.#active = {
|
|
506
|
+
target: previous.target,
|
|
507
|
+
client,
|
|
508
|
+
profile: previous.profile,
|
|
509
|
+
compatibility: previous.compatibility,
|
|
510
|
+
route: previous.route,
|
|
511
|
+
};
|
|
422
512
|
else
|
|
423
513
|
await client.close();
|
|
424
514
|
}
|
|
@@ -449,3 +539,40 @@ export class BinarySession {
|
|
|
449
539
|
}
|
|
450
540
|
}
|
|
451
541
|
const isAborted = (signal) => signal?.aborted === true;
|
|
542
|
+
const commitExecutionProfile = (operation, result, profile) => {
|
|
543
|
+
if (!result.ok || profile === undefined)
|
|
544
|
+
return result;
|
|
545
|
+
const provider = result.value.provider;
|
|
546
|
+
if (provider.id !== profile.provider.id ||
|
|
547
|
+
provider.name !== profile.provider.name ||
|
|
548
|
+
provider.version !== profile.provider.version)
|
|
549
|
+
return err(new ProviderAdapterError(profile.provider.id, `${operation}:profile`));
|
|
550
|
+
return ok({
|
|
551
|
+
...result.value,
|
|
552
|
+
analysisProfile: structuredClone(profile),
|
|
553
|
+
});
|
|
554
|
+
};
|
|
555
|
+
const capabilityStatus = (descriptor) => ({
|
|
556
|
+
operation: descriptor.operation,
|
|
557
|
+
available: descriptor.available,
|
|
558
|
+
reason: descriptor.reason,
|
|
559
|
+
input_contract_version: descriptor.inputContractVersion,
|
|
560
|
+
output_contract_version: descriptor.outputContractVersion,
|
|
561
|
+
pagination: descriptor.pagination,
|
|
562
|
+
exhaustive: descriptor.exhaustive,
|
|
563
|
+
effects: {
|
|
564
|
+
mutates_artifact: descriptor.effects.mutatesArtifact,
|
|
565
|
+
launches_process: descriptor.effects.launchesProcess,
|
|
566
|
+
may_show_ui: descriptor.effects.mayShowUi,
|
|
567
|
+
may_access_network: descriptor.effects.mayAccessNetwork,
|
|
568
|
+
may_write_filesystem: descriptor.effects.mayWriteFilesystem,
|
|
569
|
+
changes_permissions: descriptor.effects.changesPermissions,
|
|
570
|
+
requires_root: descriptor.effects.requiresRoot,
|
|
571
|
+
},
|
|
572
|
+
limits: {
|
|
573
|
+
max_results: descriptor.limits.maxResults,
|
|
574
|
+
max_payload_bytes: descriptor.limits.maxPayloadBytes,
|
|
575
|
+
timeout_ms: descriptor.limits.timeoutMs,
|
|
576
|
+
},
|
|
577
|
+
limitations: [...descriptor.limitations],
|
|
578
|
+
});
|
|
@@ -2,15 +2,13 @@ import { createEvidence, } from "../domain/evidence.js";
|
|
|
2
2
|
import { jsonValueSchema } from "../domain/jsonValue.js";
|
|
3
3
|
/** Create Evidence v2 for a policy-scoped external browser observation. */
|
|
4
4
|
export const createBrowserEvidence = (operation, input, result, provider) => createEvidence(undefined, provider, {
|
|
5
|
-
predicateType: operation
|
|
6
|
-
? "rea.browser-target-list/v1"
|
|
7
|
-
: "rea.web-page-inspection/v1",
|
|
5
|
+
predicateType: browserPredicate(operation),
|
|
8
6
|
operation,
|
|
9
7
|
parameters: browserParameters(input),
|
|
10
8
|
result: jsonValueSchema.parse(result),
|
|
11
9
|
confidence: "observed",
|
|
12
10
|
authority: "external-service",
|
|
13
|
-
environment: "
|
|
11
|
+
environment: "browser" in result
|
|
14
12
|
? {
|
|
15
13
|
id: `${result.browser.product}@${result.browser.revision}`,
|
|
16
14
|
platform: process.platform,
|
|
@@ -20,16 +18,90 @@ export const createBrowserEvidence = (operation, input, result, provider) => cre
|
|
|
20
18
|
: null,
|
|
21
19
|
limitations: result.limitations,
|
|
22
20
|
});
|
|
23
|
-
const browserParameters = (input) =>
|
|
24
|
-
cdp_endpoint
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
const browserParameters = (input) => {
|
|
22
|
+
if (!("cdp_endpoint" in input)) {
|
|
23
|
+
if ("max_changes" in input)
|
|
24
|
+
return {
|
|
25
|
+
before_target_id: input.before.inspection.target.target_id,
|
|
26
|
+
before_capture_ended_at: input.before.inspection.capture_window.ended_at,
|
|
27
|
+
after_target_id: input.after.inspection.target.target_id,
|
|
28
|
+
after_capture_ended_at: input.after.inspection.capture_window.ended_at,
|
|
29
|
+
max_changes: input.max_changes,
|
|
30
|
+
};
|
|
31
|
+
return {
|
|
32
|
+
before_artifact_uri: input.before.uri,
|
|
33
|
+
after_artifact_uri: input.after.uri,
|
|
34
|
+
channel_threshold: input.channel_threshold,
|
|
35
|
+
maximum_pixels: input.maximum_pixels,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
const scope = {
|
|
39
|
+
cdp_endpoint: input.cdp_endpoint,
|
|
40
|
+
allowed_origins: input.allowed_origins,
|
|
41
|
+
};
|
|
42
|
+
if (!("target_id" in input))
|
|
43
|
+
return { ...scope, offset: input.offset, limit: input.limit };
|
|
44
|
+
if ("screenshot_approved" in input)
|
|
45
|
+
return {
|
|
46
|
+
...scope,
|
|
47
|
+
target_id: input.target_id,
|
|
48
|
+
maximum_image_bytes: input.maximum_image_bytes,
|
|
49
|
+
};
|
|
50
|
+
if ("max_tools" in input)
|
|
51
|
+
return {
|
|
52
|
+
...scope,
|
|
28
53
|
target_id: input.target_id,
|
|
29
54
|
observation_ms: input.observation_ms,
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
55
|
+
max_tools: input.max_tools,
|
|
56
|
+
max_schema_bytes: input.max_schema_bytes,
|
|
57
|
+
max_schema_nodes: input.max_schema_nodes,
|
|
58
|
+
max_schema_depth: input.max_schema_depth,
|
|
59
|
+
};
|
|
60
|
+
if (!("include_accessibility_text" in input))
|
|
61
|
+
return {
|
|
62
|
+
...scope,
|
|
63
|
+
target_id: input.target_id,
|
|
64
|
+
observation_ms: input.observation_ms,
|
|
65
|
+
max_timeline_events: input.max_timeline_events,
|
|
66
|
+
};
|
|
67
|
+
return {
|
|
68
|
+
...scope,
|
|
69
|
+
target_id: input.target_id,
|
|
70
|
+
observation_ms: input.observation_ms,
|
|
71
|
+
include_accessibility_text: input.include_accessibility_text,
|
|
72
|
+
include_console_text: input.include_console_text,
|
|
73
|
+
include_json_body_shapes: input.include_json_body_shapes,
|
|
74
|
+
include_websocket_shapes: input.include_websocket_shapes,
|
|
75
|
+
include_script_sources: input.include_script_sources,
|
|
76
|
+
include_storage_keys: input.include_storage_keys,
|
|
77
|
+
limits: input.limits,
|
|
78
|
+
...(input.include_script_sources && "source_capture_approved" in input
|
|
79
|
+
? {
|
|
80
|
+
source_capture_approved: input.source_capture_approved,
|
|
81
|
+
fetch_source_maps: input.fetch_source_maps,
|
|
82
|
+
source_map_fetch_approved: input.source_map_fetch_approved,
|
|
83
|
+
analysis_limits: input.analysis_limits,
|
|
84
|
+
}
|
|
85
|
+
: {}),
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
const browserPredicate = (operation) => {
|
|
89
|
+
switch (operation) {
|
|
90
|
+
case "list_browser_targets":
|
|
91
|
+
return "rea.browser-target-list/v1";
|
|
92
|
+
case "inspect_web_page":
|
|
93
|
+
return "rea.web-page-inspection/v2";
|
|
94
|
+
case "analyze_web_bundle":
|
|
95
|
+
return "rea.web-bundle-analysis/v1";
|
|
96
|
+
case "observe_web_session":
|
|
97
|
+
return "rea.web-observation-session/v1";
|
|
98
|
+
case "discover_webmcp_tools":
|
|
99
|
+
return "rea.webmcp-discovery/v1";
|
|
100
|
+
case "compare_web_captures":
|
|
101
|
+
return "rea.web-capture-diff/v1";
|
|
102
|
+
case "capture_web_screenshot":
|
|
103
|
+
return "rea.web-screenshot/v1";
|
|
104
|
+
case "compare_web_screenshots":
|
|
105
|
+
return "rea.web-screenshot-diff/v1";
|
|
106
|
+
}
|
|
107
|
+
};
|