rea-agents 1.6.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.
- package/README.md +157 -66
- 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 +218 -101
- package/dist/application/CapabilityInventory.js +18 -0
- package/dist/application/CompositeProvider.js +20 -8
- package/dist/application/CrossVersionInvestigation.js +10 -6
- package/dist/application/DirectAnalysis.js +49 -35
- package/dist/application/Doctor.js +110 -8
- package/dist/application/ElectronBoundaryAnalysis.js +96 -0
- package/dist/application/ElectronBoundaryGraph.js +9 -0
- package/dist/application/ElectronBoundaryGraphContext.js +62 -0
- package/dist/application/ElectronBoundaryGraphIpc.js +242 -0
- package/dist/application/ElectronBoundaryGraphNative.js +86 -0
- package/dist/application/ElectronBoundaryGraphWindows.js +199 -0
- package/dist/application/EnhancedTools.js +39 -14
- package/dist/application/InvestigationProviders.js +41 -0
- package/dist/application/JavaScriptApplicationEvidence.js +26 -0
- package/dist/application/JavaScriptApplicationEvidenceGraph.js +89 -0
- package/dist/application/JavaScriptApplicationService.js +74 -0
- package/dist/application/JavaScriptApplicationWorkflowEvidence.js +29 -0
- package/dist/application/JavaScriptApplicationWorkflowService.js +90 -0
- package/dist/application/JavaScriptArtifactAnalysis.js +305 -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 +144 -0
- package/dist/application/JavaScriptArtifactGraphContext.js +205 -0
- package/dist/application/JavaScriptArtifactGraphDocuments.js +108 -0
- package/dist/application/JavaScriptArtifactGraphEvidence.js +99 -0
- package/dist/application/JavaScriptArtifactGraphFindings.js +271 -0
- package/dist/application/JavaScriptArtifactGraphStructure.js +409 -0
- package/dist/application/JavaScriptArtifactReconstruction.js +77 -0
- package/dist/application/JavaScriptArtifactReconstructionInput.js +18 -0
- package/dist/application/JavaScriptReplayPlanning.js +201 -0
- package/dist/application/JavaScriptReplayService.js +266 -0
- package/dist/application/JavaScriptRuntimeReconciliationEvidence.js +25 -0
- package/dist/application/JavaScriptRuntimeReconciliationService.js +27 -0
- package/dist/application/MacHopper.js +20 -4
- package/dist/application/ManagedApplicationGraphService.js +55 -0
- package/dist/application/ManagedMemberComparisonService.js +122 -0
- package/dist/application/ManagedNativeVerificationService.js +39 -0
- package/dist/application/ManagedReconstructionService.js +58 -0
- package/dist/application/ManagedRuntimeCorrelationService.js +103 -0
- package/dist/application/PermissionAuthority.js +1 -1
- package/dist/application/ProcessCaptureLifecycle.js +1 -1
- package/dist/application/ProcessHarness.js +1 -1
- package/dist/application/ProcessSampling.js +16 -12
- package/dist/application/ProjectPermissionStore.js +1 -0
- package/dist/application/SessionProviderRouter.js +219 -0
- package/dist/application/Setup.js +84 -60
- package/dist/application/SetupClients.js +1 -1
- package/dist/application/SetupPlan.js +9 -2
- package/dist/application/SupportedClients.js +40 -16
- package/dist/application/Upgrade.js +1 -1
- package/dist/application/runtime.js +11 -6
- package/dist/artifacts/AsarArtifactReader.js +23 -3
- package/dist/browser/CdpBrowserProvider.js +4 -2
- package/dist/browser/CdpCaptureEvents.js +49 -0
- package/dist/browser/CdpElectronInspection.js +106 -162
- package/dist/browser/CdpElectronProvider.js +9 -3
- package/dist/browser/CdpElectronScriptEvents.js +69 -0
- package/dist/browser/CdpElectronScripts.js +94 -0
- package/dist/browser/CdpElectronWorkers.js +56 -0
- package/dist/browser/CdpEndpoint.js +14 -1
- package/dist/browser/CdpPageCapture.js +8 -4
- package/dist/catalogIdentity.js +6 -42
- package/dist/cli.js +446 -34
- package/dist/cliApplicationCommands.js +62 -0
- package/dist/cliBrowserAdvancedCommands.js +5 -4
- package/dist/cliBrowserCommands.js +5 -4
- package/dist/cliCommandNames.js +56 -0
- package/dist/cliElectronCommands.js +106 -3
- package/dist/cliEvidenceCommands.js +4 -3
- package/dist/cliInvestigationCommands.js +2 -1
- package/dist/cliJsonInput.js +54 -0
- package/dist/cliPolicyCommands.js +2 -1
- package/dist/cliProcessCommands.js +3 -2
- package/dist/config.js +101 -2
- package/dist/contracts/applicationToolContracts.js +91 -0
- package/dist/contracts/artifactToolContracts.js +5 -9
- package/dist/contracts/browserToolContracts.js +14 -62
- package/dist/contracts/electronToolContracts.js +45 -19
- package/dist/contracts/enhancedInputs.js +2 -2
- package/dist/contracts/errorSchemas.js +1 -0
- package/dist/contracts/investigationExamples.js +0 -6
- package/dist/contracts/javascriptApplicationWorkflowExamples.js +46 -0
- package/dist/contracts/javascriptRuntimeReconciliationExample.js +208 -0
- package/dist/contracts/managedToolContracts.js +153 -0
- package/dist/contracts/managedWorkflowExamples.js +346 -0
- package/dist/contracts/managedWorkflowToolContracts.js +203 -0
- package/dist/contracts/nativeToolContracts.js +2 -6
- package/dist/contracts/promptContracts.js +18 -2
- package/dist/contracts/providerSelection.js +28 -0
- package/dist/contracts/sessionLifecycleInputs.js +2 -0
- package/dist/contracts/toolContractExamples.js +10 -25
- package/dist/contracts/toolContracts.js +69 -65
- package/dist/contracts/toolEffects.js +160 -0
- package/dist/contracts/toolOutputSchemas.js +152 -42
- package/dist/doctorRuntime.js +47 -0
- package/dist/domain/analysisProfile.js +78 -0
- package/dist/domain/analysisSnapshot.js +92 -46
- package/dist/domain/artifactComparison.js +3 -4
- package/dist/domain/artifactGraph.js +1 -0
- package/dist/domain/binaryTarget.js +4 -29
- package/dist/domain/browserObservation.js +3 -0
- package/dist/domain/bundleComparison.js +4 -4
- package/dist/domain/electronObservation.js +13 -0
- package/dist/domain/electronStaticAnalysis.js +9 -0
- package/dist/domain/electronStaticAnalysisBrowser.js +194 -0
- package/dist/domain/electronStaticAnalysisIpc.js +171 -0
- package/dist/domain/electronStaticAnalysisNative.js +164 -0
- package/dist/domain/electronStaticAnalysisTypes.js +1 -0
- package/dist/domain/electronStaticAnalysisValues.js +103 -0
- package/dist/domain/errors.js +148 -7
- 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/functionComparisonSchemas.js +3 -8
- package/dist/domain/functionDossierEvidence.js +4 -1
- package/dist/domain/hopperValues.js +66 -9
- package/dist/domain/inputIssueProjection.js +52 -0
- package/dist/domain/investigationWorkspace.js +2 -1
- package/dist/domain/javascriptApplicationAnalysis.js +155 -0
- package/dist/domain/javascriptApplicationChangeGraph.js +231 -0
- package/dist/domain/javascriptApplicationEvidenceSchemas.js +379 -0
- package/dist/domain/javascriptApplicationGraph.js +282 -0
- package/dist/domain/javascriptApplicationGraphSchemas.js +139 -0
- package/dist/domain/javascriptApplicationVersionComparison.js +116 -0
- package/dist/domain/javascriptApplicationVersionComparisonSchemas.js +139 -0
- package/dist/domain/javascriptApplicationVersionItems.js +221 -0
- package/dist/domain/javascriptApplicationVersionKeys.js +170 -0
- package/dist/domain/javascriptAstFingerprint.js +143 -0
- package/dist/domain/javascriptFeatureSeed.js +140 -0
- package/dist/domain/javascriptFeatureTrace.js +232 -0
- package/dist/domain/javascriptFeatureTraceSchemas.js +136 -0
- package/dist/domain/javascriptFeatureTraversal.js +88 -0
- package/dist/domain/javascriptNativeHandoff.js +85 -0
- package/dist/domain/javascriptReplay.js +361 -0
- package/dist/domain/javascriptRuntimeLoadState.js +106 -0
- package/dist/domain/javascriptRuntimeReconciliation.js +42 -0
- package/dist/domain/javascriptRuntimeReconciliationGraph.js +169 -0
- package/dist/domain/javascriptRuntimeReconciliationMatching.js +247 -0
- package/dist/domain/javascriptRuntimeReconciliationParsing.js +160 -0
- package/dist/domain/javascriptRuntimeReconciliationResult.js +119 -0
- package/dist/domain/javascriptRuntimeReconciliationRuntime.js +316 -0
- package/dist/domain/javascriptRuntimeReconciliationSchemas.js +245 -0
- package/dist/domain/javascriptRuntimeStaticCandidates.js +191 -0
- package/dist/domain/javascriptStaticAnalysis.js +346 -0
- package/dist/domain/javascriptStaticAnalysisFindings.js +39 -0
- package/dist/domain/javascriptStaticAnalysisHelpers.js +274 -0
- package/dist/domain/javascriptStaticAnalysisState.js +23 -0
- package/dist/domain/javascriptStaticAnalysisTypes.js +1 -0
- package/dist/domain/managedApplicationGraph.js +556 -0
- package/dist/domain/managedArtifact.js +444 -0
- package/dist/domain/managedMemberComparison.js +565 -0
- package/dist/domain/managedNativeVerification.js +408 -0
- package/dist/domain/managedReconstruction.js +183 -0
- package/dist/domain/managedRuntimeCorrelation.js +175 -0
- package/dist/domain/reconstructionVerificationSchemas.js +1 -3
- package/dist/dotnet/ManagedArtifactInspector.js +318 -0
- package/dist/dotnet/ManagedMemberInspector.js +887 -0
- package/dist/dotnet/ManagedMetadataHeaps.js +131 -0
- package/dist/dotnet/ManagedMetadataInventory.js +368 -0
- package/dist/dotnet/ManagedMetadataLayout.js +322 -0
- package/dist/dotnet/ManagedNativeBoundaryInspector.js +391 -0
- package/dist/dotnet/ManagedPeReader.js +157 -0
- package/dist/dotnet/ManagedReaderFailure.js +10 -0
- package/dist/dotnet/ManagedStaticProvider.js +203 -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 +366 -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 +19 -54
- package/dist/hopper/HopperAnalysisProfile.js +106 -0
- package/dist/hopper/HopperClient.js +154 -162
- package/dist/hopper/HopperProvider.js +99 -7
- package/dist/hopper/HopperSessionValues.js +27 -0
- package/dist/hopper/HopperSocketConnection.js +31 -0
- package/dist/main.js +14 -0
- package/dist/native/NativeMacOSProvider.js +3 -1
- package/dist/process/PendingOperations.js +59 -0
- package/dist/process/PrivateRuntimeRoot.js +33 -0
- package/dist/process/ProviderDeadline.js +95 -0
- package/dist/process/ProviderProcess.js +275 -0
- package/dist/replay/JavaScriptReplayWorker.js +286 -0
- package/dist/replay/LinuxJavaScriptReplayRunner.js +434 -0
- package/dist/replay/LinuxRuntimeClosure.js +46 -0
- package/dist/replay/LinuxSeccompPolicy.js +40 -0
- package/dist/replay/ReplayWorkerProtocol.js +55 -0
- package/dist/replay/SystemJavaScriptReplayHost.js +182 -0
- package/dist/server/createServer.js +53 -0
- package/dist/server/promptCompletion.js +9 -3
- package/dist/server/registerApplicationTools.js +115 -0
- package/dist/server/registerArtifactComparisonTool.js +16 -9
- package/dist/server/registerBrowserTools.js +33 -8
- package/dist/server/registerBundleComparisonTool.js +17 -20
- package/dist/server/registerElectronTools.js +45 -3
- package/dist/server/registerEnhancedTools.js +67 -19
- package/dist/server/registerEvidenceTools.js +5 -1
- package/dist/server/registerFunctionComparisonTool.js +14 -7
- package/dist/server/registerInvestigationTools.js +21 -17
- package/dist/server/registerManagedTools.js +6 -0
- package/dist/server/registerManagedWorkflowTools.js +277 -0
- package/dist/server/registerOfficialTools.js +11 -8
- package/dist/server/registerProcessComparisonTool.js +25 -47
- package/dist/server/registerSessionStatusTool.js +7 -7
- package/dist/server/registerSessionTools.js +57 -87
- package/dist/server/sessionEvidence.js +14 -19
- package/dist/server/toolInputValidation.js +77 -0
- package/dist/server/toolRegistrationOptions.js +79 -2
- package/dist/server/toolResult.js +25 -17
- package/package.json +17 -7
- package/scripts/rea.mjs +36 -2
- package/skills/rea-analysis/SKILL.md +59 -3
- /package/dist/{application → process}/ProcessOwnership.js +0 -0
|
@@ -13,20 +13,19 @@ import { snapshotEvidenceForQuery, snapshotMatchesTarget, } from "../domain/anal
|
|
|
13
13
|
import { err, ok } from "../domain/result.js";
|
|
14
14
|
import { loadConfiguredPermissionAuthority } from "./PermissionConfiguration.js";
|
|
15
15
|
import { authorizeFileReadWithDeferredWrite, } from "./DeferredFileAuthorization.js";
|
|
16
|
-
|
|
17
|
-
id: "rea-workflow",
|
|
18
|
-
name: "REA composed investigation workflow",
|
|
19
|
-
version: "1",
|
|
20
|
-
};
|
|
16
|
+
import { REA_WORKFLOW_PROVIDER, workflowAnalysisProfile, } from "./InvestigationProviders.js";
|
|
21
17
|
/**
|
|
22
|
-
* Open one binary, execute one tool, and always release
|
|
18
|
+
* Open one binary, execute one tool, and always release provider resources.
|
|
23
19
|
* Unlike MCP mode, every CLI invocation is intentionally isolated and does not
|
|
24
|
-
* retain a target or
|
|
20
|
+
* retain a target or provider client for a subsequent command.
|
|
25
21
|
*/
|
|
26
22
|
export const runDirectAnalysis = async (path, tool, arguments_, options = {}) => withProcessCancellation(options.signal, (signal) => runAnalysis(path, tool, arguments_, {
|
|
27
23
|
logger: options.logger ?? silentLogger,
|
|
28
24
|
snapshotPath: options.snapshotPath,
|
|
29
25
|
signal,
|
|
26
|
+
...(options.providerId === undefined
|
|
27
|
+
? {}
|
|
28
|
+
: { providerId: options.providerId }),
|
|
30
29
|
...(options.permissionAuthority === undefined
|
|
31
30
|
? {}
|
|
32
31
|
: { permissionAuthority: options.permissionAuthority }),
|
|
@@ -37,7 +36,7 @@ export const runProviderAnalysis = async (path, tool, arguments_, logger = silen
|
|
|
37
36
|
snapshotPath: undefined,
|
|
38
37
|
signal: operationSignal,
|
|
39
38
|
}));
|
|
40
|
-
/** Describe configured providers without opening a target or
|
|
39
|
+
/** Describe configured providers without opening a target or starting one. */
|
|
41
40
|
export const runSessionStatus = async (logger = silentLogger) => {
|
|
42
41
|
const config = parseConfig(process.env);
|
|
43
42
|
if (!config.ok)
|
|
@@ -135,30 +134,44 @@ const runAnalysis = async (path, tool, arguments_, options) => {
|
|
|
135
134
|
});
|
|
136
135
|
if (!authorization.ok)
|
|
137
136
|
return cliError(authorization.error);
|
|
138
|
-
const unavailable = snapshotUnavailable(snapshotPath, config.value.hopperLoaderArgs);
|
|
139
|
-
if (unavailable !== undefined)
|
|
140
|
-
return cliError(unavailable);
|
|
141
137
|
const session = createBinarySession(config.value, logger);
|
|
142
138
|
try {
|
|
143
139
|
const prepared = await prepareSnapshot({
|
|
144
140
|
path,
|
|
145
|
-
tool,
|
|
146
|
-
arguments: arguments_,
|
|
147
141
|
snapshotPath,
|
|
148
142
|
policy: config.value.analysisSnapshotFilePolicy,
|
|
149
|
-
provider: replayProviderFor(session, tool),
|
|
150
143
|
});
|
|
151
144
|
if (!prepared.ok)
|
|
152
145
|
return cliError(prepared.error);
|
|
153
|
-
if (prepared.value.evidence !== undefined)
|
|
154
|
-
return prepared.value.evidence;
|
|
155
|
-
const writable = await authorizeDeferredWrite(authorization.value);
|
|
156
|
-
if (!writable.ok)
|
|
157
|
-
return cliError(writable.error);
|
|
158
146
|
const { snapshot } = prepared.value;
|
|
159
|
-
const opened = await session.open(path,
|
|
147
|
+
const opened = await session.open(path, {
|
|
148
|
+
signal,
|
|
149
|
+
...(snapshot === undefined ? {} : { snapshot }),
|
|
150
|
+
...(options.providerId === undefined
|
|
151
|
+
? {}
|
|
152
|
+
: { providerId: options.providerId }),
|
|
153
|
+
});
|
|
160
154
|
if (!opened.ok)
|
|
161
155
|
return cliError(opened.error);
|
|
156
|
+
const evidenceProfile = analysisProfileForEvidence(session, tool);
|
|
157
|
+
const bindingProfile = session.analysisProfile();
|
|
158
|
+
if (snapshot !== undefined &&
|
|
159
|
+
evidenceProfile !== undefined &&
|
|
160
|
+
bindingProfile !== undefined) {
|
|
161
|
+
const cached = snapshotEvidenceForQuery(snapshot, {
|
|
162
|
+
target: opened.value,
|
|
163
|
+
bindingProfile,
|
|
164
|
+
operation: tool,
|
|
165
|
+
parameters: arguments_,
|
|
166
|
+
provider: replayProviderFor(session, tool),
|
|
167
|
+
evidenceProfile,
|
|
168
|
+
});
|
|
169
|
+
if (cached !== undefined)
|
|
170
|
+
return cached;
|
|
171
|
+
}
|
|
172
|
+
const writable = await authorizeDeferredWrite(authorization.value);
|
|
173
|
+
if (!writable.ok)
|
|
174
|
+
return cliError(writable.error);
|
|
162
175
|
let output;
|
|
163
176
|
let evidence;
|
|
164
177
|
if (tool === "binary_overview" ||
|
|
@@ -170,10 +183,13 @@ const runAnalysis = async (path, tool, arguments_, options) => {
|
|
|
170
183
|
else {
|
|
171
184
|
evidence = createEvidence(opened.value, tool === "analyze_function"
|
|
172
185
|
? session.providerIdentity(tool)
|
|
173
|
-
:
|
|
186
|
+
: REA_WORKFLOW_PROVIDER, {
|
|
174
187
|
operation: tool,
|
|
175
188
|
parameters: arguments_,
|
|
176
189
|
result: result.value,
|
|
190
|
+
...(evidenceProfile === undefined
|
|
191
|
+
? {}
|
|
192
|
+
: { analysisProfile: evidenceProfile }),
|
|
177
193
|
confidence: "derived",
|
|
178
194
|
limitations: ["Derived by an REA composed workflow."],
|
|
179
195
|
});
|
|
@@ -189,6 +205,9 @@ const runAnalysis = async (path, tool, arguments_, options) => {
|
|
|
189
205
|
operation: tool,
|
|
190
206
|
parameters: arguments_,
|
|
191
207
|
result: result.value.result,
|
|
208
|
+
...(result.value.analysisProfile === undefined
|
|
209
|
+
? {}
|
|
210
|
+
: { analysisProfile: result.value.analysisProfile }),
|
|
192
211
|
rawResult: result.value.rawResult,
|
|
193
212
|
limitations: result.value.limitations,
|
|
194
213
|
locations: result.value.locations,
|
|
@@ -226,7 +245,7 @@ const withProcessCancellation = async (suppliedSignal, operation) => {
|
|
|
226
245
|
}
|
|
227
246
|
};
|
|
228
247
|
const prepareSnapshot = async (options) => {
|
|
229
|
-
const { path,
|
|
248
|
+
const { path, snapshotPath, policy } = options;
|
|
230
249
|
if (snapshotPath === undefined || !(await fileExists(snapshotPath)))
|
|
231
250
|
return ok({});
|
|
232
251
|
const loaded = await readAnalysisSnapshot(snapshotPath, policy);
|
|
@@ -237,22 +256,17 @@ const prepareSnapshot = async (options) => {
|
|
|
237
256
|
return target;
|
|
238
257
|
if (!snapshotMatchesTarget(loaded.value.target, target.value))
|
|
239
258
|
return err(new EvidenceIntegrityError("Analysis snapshot target does not match the requested binary"));
|
|
240
|
-
|
|
241
|
-
target: target.value,
|
|
242
|
-
operation: tool,
|
|
243
|
-
parameters: arguments_,
|
|
244
|
-
provider,
|
|
245
|
-
});
|
|
246
|
-
return ok(evidence === undefined
|
|
247
|
-
? { snapshot: loaded.value }
|
|
248
|
-
: { snapshot: loaded.value, evidence });
|
|
259
|
+
return ok({ snapshot: loaded.value });
|
|
249
260
|
};
|
|
250
|
-
const snapshotUnavailable = (snapshotPath, loaderArgs) => snapshotPath !== undefined && loaderArgs.length > 0
|
|
251
|
-
? new EvidenceIntegrityError("Analysis snapshots are unavailable with custom Hopper loader arguments")
|
|
252
|
-
: undefined;
|
|
253
261
|
const replayProviderFor = (session, tool) => tool === "binary_overview" || tool === "trace_feature"
|
|
254
|
-
?
|
|
262
|
+
? REA_WORKFLOW_PROVIDER
|
|
255
263
|
: session.providerIdentity(tool);
|
|
264
|
+
const analysisProfileForEvidence = (session, tool) => {
|
|
265
|
+
if (tool !== "binary_overview" && tool !== "trace_feature")
|
|
266
|
+
return session.analysisProfile(tool);
|
|
267
|
+
const upstream = session.analysisProfile();
|
|
268
|
+
return upstream === undefined ? undefined : workflowAnalysisProfile(upstream);
|
|
269
|
+
};
|
|
256
270
|
const fileExists = async (path) => {
|
|
257
271
|
try {
|
|
258
272
|
await access(path);
|
|
@@ -65,6 +65,10 @@ export const runDoctor = async (target, host = systemDoctorHost()) => {
|
|
|
65
65
|
remediation: unsupportedHopperRemediation(host.configuredHopperPath === hopperPath),
|
|
66
66
|
classification: "config_drift",
|
|
67
67
|
}));
|
|
68
|
+
const providerInspections = await inspectDoctorProviders(host, checks);
|
|
69
|
+
const javascriptReplayCheck = await host.javascriptReplayCheck?.();
|
|
70
|
+
if (javascriptReplayCheck !== undefined)
|
|
71
|
+
checks.push(javascriptReplayCheck);
|
|
68
72
|
if (host.platform === "linux" && hopperPath !== undefined)
|
|
69
73
|
checks.push(check("hopper-demo-runtime", await host.linuxDemoRuntimeReady(), undefined, {
|
|
70
74
|
remediation: "Rerun rea setup to install the Xvfb, xauth, Python, X11, and XTEST packages required for Linux demo sessions.",
|
|
@@ -76,7 +80,33 @@ export const runDoctor = async (target, host = systemDoctorHost()) => {
|
|
|
76
80
|
classification: "config_drift",
|
|
77
81
|
}));
|
|
78
82
|
const installationPaths = (await host.installationPaths?.()) ?? [];
|
|
79
|
-
const
|
|
83
|
+
const observedSkillIdentity = await host.installedSkillIdentity?.();
|
|
84
|
+
const legacySkillVersion = observedSkillIdentity === undefined
|
|
85
|
+
? await host.installedSkillVersion?.()
|
|
86
|
+
: undefined;
|
|
87
|
+
const installedSkillIdentity = observedSkillIdentity ??
|
|
88
|
+
(legacySkillVersion === undefined
|
|
89
|
+
? undefined
|
|
90
|
+
: {
|
|
91
|
+
version: legacySkillVersion,
|
|
92
|
+
toolCount: null,
|
|
93
|
+
catalogDigest: null,
|
|
94
|
+
});
|
|
95
|
+
const installedSkillVersion = installedSkillIdentity?.version ?? undefined;
|
|
96
|
+
const skillAligned = installedSkillIdentity?.version === PRODUCT_IDENTITY.skillVersion &&
|
|
97
|
+
installedSkillIdentity.toolCount === CATALOG_IDENTITY.counts.mcp_tools &&
|
|
98
|
+
installedSkillIdentity.catalogDigest ===
|
|
99
|
+
CATALOG_IDENTITY.digests.combined_sha256;
|
|
100
|
+
if (!skillAligned)
|
|
101
|
+
checks.push({
|
|
102
|
+
name: "skill:identity",
|
|
103
|
+
ok: false,
|
|
104
|
+
classification: "config_drift",
|
|
105
|
+
detail: installedSkillIdentity === undefined
|
|
106
|
+
? "Installed REA skill identity is missing."
|
|
107
|
+
: "Installed REA skill identity is stale.",
|
|
108
|
+
remediation: "Run rea setup to update the installed REA skill.",
|
|
109
|
+
});
|
|
80
110
|
const registrations = (await host.clientRegistrations?.()) ?? [];
|
|
81
111
|
for (const registration of registrations)
|
|
82
112
|
if (registration.state !== "aligned")
|
|
@@ -89,8 +119,16 @@ export const runDoctor = async (target, host = systemDoctorHost()) => {
|
|
|
89
119
|
"Run rea setup, then restart the affected client.",
|
|
90
120
|
});
|
|
91
121
|
return {
|
|
92
|
-
healthy: checks
|
|
122
|
+
healthy: doctorHealthy(checks, {
|
|
123
|
+
hopperAvailable: hopperPath !== undefined &&
|
|
124
|
+
checks
|
|
125
|
+
.filter(({ name }) => name.startsWith("hopper"))
|
|
126
|
+
.every(({ ok }) => ok),
|
|
127
|
+
hopperConfigured: host.configuredHopperPath !== undefined,
|
|
128
|
+
providerInspections,
|
|
129
|
+
}),
|
|
93
130
|
...(hopperPath === undefined ? {} : { hopperPath }),
|
|
131
|
+
...(providerInspections === undefined ? {} : { providerInspections }),
|
|
94
132
|
checks,
|
|
95
133
|
identity: {
|
|
96
134
|
cli_package_version: PRODUCT_IDENTITY.packageVersion,
|
|
@@ -111,13 +149,14 @@ export const runDoctor = async (target, host = systemDoctorHost()) => {
|
|
|
111
149
|
},
|
|
112
150
|
skill: {
|
|
113
151
|
installed_version: installedSkillVersion ?? null,
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
152
|
+
installed_tool_count: installedSkillIdentity?.toolCount ?? null,
|
|
153
|
+
installed_catalog_digest: installedSkillIdentity?.catalogDigest ?? null,
|
|
154
|
+
state: installedSkillIdentity === undefined
|
|
155
|
+
? "missing"
|
|
156
|
+
: skillAligned
|
|
117
157
|
? "aligned"
|
|
118
158
|
: "stale",
|
|
119
|
-
remediation:
|
|
120
|
-
installedSkillVersion === PRODUCT_IDENTITY.skillVersion
|
|
159
|
+
remediation: skillAligned
|
|
121
160
|
? null
|
|
122
161
|
: "Run rea setup to update the installed REA skill.",
|
|
123
162
|
},
|
|
@@ -129,7 +168,7 @@ const unsupportedHopperRemediation = (configured) => configured
|
|
|
129
168
|
? "Unset or update HOPPER_LAUNCHER_PATH, install the Hopper build supported by this REA release, or update REA."
|
|
130
169
|
: "Install the Hopper build supported by this REA release, or update REA for a newer Hopper build.";
|
|
131
170
|
/** Create diagnostics backed by the current process and host commands. */
|
|
132
|
-
export const systemDoctorHost = () => ({
|
|
171
|
+
export const systemDoctorHost = (options = {}) => ({
|
|
133
172
|
platform: process.platform,
|
|
134
173
|
nodeVersion: process.versions.node,
|
|
135
174
|
...(process.env.HOPPER_LAUNCHER_PATH === undefined
|
|
@@ -208,6 +247,12 @@ export const systemDoctorHost = () => ({
|
|
|
208
247
|
}
|
|
209
248
|
return paths;
|
|
210
249
|
},
|
|
250
|
+
...(options.providerInspections === undefined
|
|
251
|
+
? {}
|
|
252
|
+
: { providerInspections: options.providerInspections }),
|
|
253
|
+
...(options.javascriptReplayCheck === undefined
|
|
254
|
+
? {}
|
|
255
|
+
: { javascriptReplayCheck: options.javascriptReplayCheck }),
|
|
211
256
|
async installationPaths() {
|
|
212
257
|
try {
|
|
213
258
|
const command = process.platform === "win32" ? "where" : "which";
|
|
@@ -227,6 +272,23 @@ export const systemDoctorHost = () => ({
|
|
|
227
272
|
return undefined;
|
|
228
273
|
}
|
|
229
274
|
},
|
|
275
|
+
async installedSkillIdentity() {
|
|
276
|
+
try {
|
|
277
|
+
const content = await readFile(join(homedir(), ".agents/skills", PRODUCT_IDENTITY.skillName, "SKILL.md"), "utf8");
|
|
278
|
+
const version = /^\s{2}version:\s*"([^"]+)"\s*$/mu.exec(content)?.[1] ?? null;
|
|
279
|
+
const countText = /^\s{2}tool_count:\s*(\d+)\s*$/mu.exec(content)?.[1];
|
|
280
|
+
const catalogDigest = /^\s{2}catalog_digest:\s*"([a-f0-9]{64})"\s*$/mu.exec(content)?.[1] ??
|
|
281
|
+
null;
|
|
282
|
+
return {
|
|
283
|
+
version,
|
|
284
|
+
toolCount: countText === undefined ? null : Number.parseInt(countText, 10),
|
|
285
|
+
catalogDigest,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
return undefined;
|
|
290
|
+
}
|
|
291
|
+
},
|
|
230
292
|
clientRegistrations: () => readClientRegistrationStatuses(homedir()),
|
|
231
293
|
});
|
|
232
294
|
const parseMajor = (version) => Number.parseInt(version.split(".")[0] ?? "0", 10);
|
|
@@ -243,3 +305,43 @@ const check = (name, ok, detail, failure) => ({
|
|
|
243
305
|
...(detail === undefined ? {} : { detail }),
|
|
244
306
|
...(ok ? {} : { remediation: failure.remediation }),
|
|
245
307
|
});
|
|
308
|
+
const providerDoctorChecks = (inspection) => inspection.checks.map((candidate) => ({
|
|
309
|
+
name: candidate.name === "configuration"
|
|
310
|
+
? inspection.id
|
|
311
|
+
: `${inspection.id}-${candidate.name}`,
|
|
312
|
+
ok: candidate.ok,
|
|
313
|
+
classification: candidate.ok ? "healthy" : candidate.classification,
|
|
314
|
+
detail: candidate.detail,
|
|
315
|
+
...(candidate.remediation === null
|
|
316
|
+
? {}
|
|
317
|
+
: { remediation: candidate.remediation }),
|
|
318
|
+
}));
|
|
319
|
+
const inspectDoctorProviders = async (host, checks) => {
|
|
320
|
+
const inspections = await host.providerInspections?.();
|
|
321
|
+
if (inspections === undefined)
|
|
322
|
+
return undefined;
|
|
323
|
+
for (const inspection of inspections)
|
|
324
|
+
checks.push(...providerDoctorChecks(inspection));
|
|
325
|
+
return inspections;
|
|
326
|
+
};
|
|
327
|
+
const doctorHealthy = (checks, providers) => {
|
|
328
|
+
if (providers.providerInspections === undefined)
|
|
329
|
+
return checks.every(({ ok }) => ok);
|
|
330
|
+
const providerChecks = checks.filter(({ name }) => name.startsWith("hopper") ||
|
|
331
|
+
providers.providerInspections?.some((inspection) => providerCheckName(inspection.id, name)) === true);
|
|
332
|
+
const coreHealthy = checks
|
|
333
|
+
.filter((candidate) => !providerChecks.includes(candidate))
|
|
334
|
+
.every(({ ok }) => ok);
|
|
335
|
+
const configuredProvidersHealthy = providerChecks
|
|
336
|
+
.filter(({ name }) => {
|
|
337
|
+
if (name.startsWith("hopper"))
|
|
338
|
+
return providers.hopperConfigured;
|
|
339
|
+
return providers.providerInspections?.some((inspection) => inspection.configured && providerCheckName(inspection.id, name));
|
|
340
|
+
})
|
|
341
|
+
.every(({ ok }) => ok);
|
|
342
|
+
return (coreHealthy &&
|
|
343
|
+
configuredProvidersHealthy &&
|
|
344
|
+
(providers.hopperAvailable ||
|
|
345
|
+
providers.providerInspections.some(({ available }) => available)));
|
|
346
|
+
};
|
|
347
|
+
const providerCheckName = (providerId, name) => name === providerId || name.startsWith(`${providerId}-`);
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { resolveArtifactPath } from "./JavaScriptArtifactGraphContext.js";
|
|
2
|
+
/** Flatten all per-file IPC facts into deterministic application records. */
|
|
3
|
+
export const collectElectronIpcRecords = (analysis) => analysis.files
|
|
4
|
+
.flatMap(({ file, javascript }) => (javascript?.electron.ipc ?? []).map((finding) => ({
|
|
5
|
+
key: ipcRecordKey(file.path, finding),
|
|
6
|
+
file,
|
|
7
|
+
finding,
|
|
8
|
+
})))
|
|
9
|
+
.sort((left, right) => compareCodePoints(left.key, right.key));
|
|
10
|
+
/** Match only exact literal channels and retain ambiguous matches separately. */
|
|
11
|
+
export const classifyElectronIpcPairings = (records) => {
|
|
12
|
+
const handlers = records.filter(isMainHandler);
|
|
13
|
+
return records.filter(isPairableRendererTransmission).map((transmission) => {
|
|
14
|
+
const candidates = transmission.finding.channel === null
|
|
15
|
+
? []
|
|
16
|
+
: handlers.filter((handler) => handler.finding.channel === transmission.finding.channel &&
|
|
17
|
+
compatibleIpcModes(transmission.finding, handler.finding));
|
|
18
|
+
return {
|
|
19
|
+
transmission,
|
|
20
|
+
candidates,
|
|
21
|
+
status: candidates.length === 1
|
|
22
|
+
? "paired"
|
|
23
|
+
: candidates.length > 1
|
|
24
|
+
? "ambiguous"
|
|
25
|
+
: "unpaired",
|
|
26
|
+
};
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
/** Return only pairings supported by one unique literal main handler. */
|
|
30
|
+
export const unambiguousElectronIpcPairings = (records) => classifyElectronIpcPairings(records).flatMap((state) => {
|
|
31
|
+
const handler = state.candidates[0];
|
|
32
|
+
return state.status === "paired" && handler !== undefined
|
|
33
|
+
? [{ transmission: state.transmission, handler }]
|
|
34
|
+
: [];
|
|
35
|
+
});
|
|
36
|
+
/** Summarize static Electron findings without treating gaps as absence. */
|
|
37
|
+
export const summarizeElectronBoundaries = (analysis) => {
|
|
38
|
+
const files = new Map(analysis.files.map(({ file }) => [file.path, file]));
|
|
39
|
+
const javascript = analysis.files.flatMap(({ javascript: value }) => value === null ? [] : [value]);
|
|
40
|
+
const windows = javascript.flatMap(({ electron }) => electron.browser_windows);
|
|
41
|
+
const bridges = javascript.flatMap(({ electron }) => electron.context_bridge_apis);
|
|
42
|
+
const validations = javascript.flatMap(({ electron }) => electron.sender_validations);
|
|
43
|
+
const utilities = analysis.files.flatMap(({ file, javascript: value }) => (value?.electron.utility_processes ?? []).map((finding) => ({
|
|
44
|
+
file,
|
|
45
|
+
finding,
|
|
46
|
+
})));
|
|
47
|
+
const nativeBindings = analysis.files.flatMap(({ file, javascript: value }) => (value?.electron.native_addon_bindings ?? []).map((finding) => ({
|
|
48
|
+
file,
|
|
49
|
+
finding,
|
|
50
|
+
})));
|
|
51
|
+
const ipc = collectElectronIpcRecords(analysis);
|
|
52
|
+
const pairing = classifyElectronIpcPairings(ipc);
|
|
53
|
+
const literalChannels = new Set(ipc.flatMap(({ finding }) => finding.channel === null ? [] : [finding.channel]));
|
|
54
|
+
return {
|
|
55
|
+
browser_windows: windows.length,
|
|
56
|
+
explicit_web_preferences: windows.reduce((count, window) => count + window.web_preferences.length, 0),
|
|
57
|
+
preload_entrypoints: windows.filter(({ preload_path: path }) => path !== null).length,
|
|
58
|
+
context_bridge_apis: bridges.length,
|
|
59
|
+
exposed_api_members: bridges.reduce((count, bridge) => count + bridge.members.length, 0),
|
|
60
|
+
ipc: {
|
|
61
|
+
operations: ipc.length,
|
|
62
|
+
literal_channels: literalChannels.size,
|
|
63
|
+
dynamic_channel_operations: ipc.filter(({ finding }) => finding.channel === null).length,
|
|
64
|
+
renderer_transmissions: ipc.filter(isRendererTransmission).length,
|
|
65
|
+
renderer_listeners: ipc.filter(({ finding }) => finding.side === "renderer" && finding.mode === "listen").length,
|
|
66
|
+
main_handlers: ipc.filter(isMainHandler).length,
|
|
67
|
+
paired_renderer_transmissions: pairing.filter(({ status }) => status === "paired").length,
|
|
68
|
+
ambiguous_renderer_transmissions: pairing.filter(({ status }) => status === "ambiguous").length,
|
|
69
|
+
unpaired_literal_renderer_transmissions: pairing.filter(({ status, transmission }) => status === "unpaired" && transmission.finding.channel !== null).length,
|
|
70
|
+
},
|
|
71
|
+
sender_validation_observations: validations.length,
|
|
72
|
+
utility_processes: utilities.length,
|
|
73
|
+
resolved_utility_entrypoints: utilities.filter(({ file, finding }) => resolvesToFile(file, finding.module_path, files)).length,
|
|
74
|
+
native_addon_bindings: nativeBindings.length,
|
|
75
|
+
resolved_native_addon_bindings: nativeBindings.filter(({ file, finding }) => resolvesToFile(file, finding.specifier, files, "native-addon")).length,
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
const compatibleIpcModes = (transmission, handler) => transmission.mode === "invoke"
|
|
79
|
+
? handler.mode === "handle"
|
|
80
|
+
: transmission.operation === "send-to-host"
|
|
81
|
+
? false
|
|
82
|
+
: transmission.mode === "send" && handler.mode === "listen";
|
|
83
|
+
const isRendererTransmission = ({ finding }) => finding.side === "renderer" &&
|
|
84
|
+
(finding.mode === "send" || finding.mode === "invoke");
|
|
85
|
+
const isPairableRendererTransmission = (record) => isRendererTransmission(record) && record.finding.operation !== "send-to-host";
|
|
86
|
+
const isMainHandler = ({ finding }) => finding.side === "main" &&
|
|
87
|
+
(finding.mode === "listen" || finding.mode === "handle");
|
|
88
|
+
const resolvesToFile = (file, specifier, files, kind) => {
|
|
89
|
+
if (specifier === null)
|
|
90
|
+
return false;
|
|
91
|
+
const resolved = resolveArtifactPath(specifier, file.path, files);
|
|
92
|
+
return (resolved !== null &&
|
|
93
|
+
(kind === undefined || files.get(resolved)?.kind === kind));
|
|
94
|
+
};
|
|
95
|
+
const ipcRecordKey = (path, finding) => `${path}\0${String(finding.location.start.line)}:${String(finding.location.start.column)}\0${finding.side}\0${finding.operation}`;
|
|
96
|
+
const compareCodePoints = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { addElectronIpcBoundaries } from "./ElectronBoundaryGraphIpc.js";
|
|
2
|
+
import { addElectronNativeBoundaries } from "./ElectronBoundaryGraphNative.js";
|
|
3
|
+
import { addElectronWindowBoundaries } from "./ElectronBoundaryGraphWindows.js";
|
|
4
|
+
/** Add every static Electron process and security-boundary fact to the JAG. */
|
|
5
|
+
export const addElectronBoundaries = (context) => {
|
|
6
|
+
addElectronWindowBoundaries(context);
|
|
7
|
+
addElectronIpcBoundaries(context);
|
|
8
|
+
addElectronNativeBoundaries(context);
|
|
9
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { sha256Text } from "../domain/javascriptStaticAnalysisHelpers.js";
|
|
2
|
+
import { astObservationEvidence, staticInferenceEvidence, } from "./JavaScriptArtifactGraphEvidence.js";
|
|
3
|
+
/** Resolve the file or recovered bundle module that owns one finding. */
|
|
4
|
+
export const electronFindingSourceNode = (context, file, moduleKey) => moduleKey === null
|
|
5
|
+
? (context.assetNodes.get(file.path) ?? context.fileNodes.get(file.path))
|
|
6
|
+
: context.moduleNodes.get(`${file.path}\0${moduleKey}`);
|
|
7
|
+
/** Observation-scoped identity shared safely across different artifact files. */
|
|
8
|
+
export const electronObservationIdentity = (context, scope, key) => ({
|
|
9
|
+
strategy: "observation-fingerprint",
|
|
10
|
+
stability: "observation-only",
|
|
11
|
+
observation_sha256: sha256Text(`${context.snapshot.manifest.root_sha256}\0${scope}\0${key}`),
|
|
12
|
+
scope: scope.slice(0, 4_096),
|
|
13
|
+
});
|
|
14
|
+
/** Add a direct AST syntax relationship. */
|
|
15
|
+
export const addElectronAstEdge = (context, input) => {
|
|
16
|
+
context.accumulator.addEdge({
|
|
17
|
+
source_node_id: input.source.node_id,
|
|
18
|
+
target_node_id: input.target.node_id,
|
|
19
|
+
relation: input.relation,
|
|
20
|
+
properties: input.properties,
|
|
21
|
+
evidence: astObservationEvidence({
|
|
22
|
+
sha256: input.file.sha256,
|
|
23
|
+
path: input.file.path,
|
|
24
|
+
range: input.range,
|
|
25
|
+
operation: input.operation,
|
|
26
|
+
coverage: input.coverage,
|
|
27
|
+
...(input.limitations === undefined
|
|
28
|
+
? {}
|
|
29
|
+
: { limitations: input.limitations }),
|
|
30
|
+
}),
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
/** Add an Electron relationship that static syntax suggests but cannot prove. */
|
|
34
|
+
export const addElectronInferenceEdge = (context, input) => {
|
|
35
|
+
context.accumulator.addEdge({
|
|
36
|
+
source_node_id: input.source.node_id,
|
|
37
|
+
target_node_id: input.target.node_id,
|
|
38
|
+
relation: input.relation,
|
|
39
|
+
properties: input.properties,
|
|
40
|
+
evidence: staticInferenceEvidence({
|
|
41
|
+
sha256: input.file.sha256,
|
|
42
|
+
path: input.file.path,
|
|
43
|
+
range: input.range,
|
|
44
|
+
operation: input.operation,
|
|
45
|
+
coverage: input.coverage,
|
|
46
|
+
...(input.confidence === undefined
|
|
47
|
+
? {}
|
|
48
|
+
: { confidence: input.confidence }),
|
|
49
|
+
...(input.limitations === undefined
|
|
50
|
+
? {}
|
|
51
|
+
: { limitations: input.limitations }),
|
|
52
|
+
}),
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
/** Deterministic source-range key for artifact-local graph identities. */
|
|
56
|
+
export const electronRangeKey = (range) => `${String(range.start.line)}:${String(range.start.column)}-${String(range.end.line)}:${String(range.end.column)}`;
|
|
57
|
+
/** Test exact source-range containment without assuming runtime reachability. */
|
|
58
|
+
export const electronRangeContains = (outer, inner) => comparePoint(outer.start, inner.start) <= 0 &&
|
|
59
|
+
comparePoint(outer.end, inner.end) >= 0;
|
|
60
|
+
const comparePoint = (left, right) => left.line === right.line
|
|
61
|
+
? left.column - right.column
|
|
62
|
+
: left.line - right.line;
|