rea-agents 2.4.0 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -18
- package/bridge/ghidra/ReaGhidraBridge.java +48 -1
- package/bridge/hopper_bridge.py +259 -24
- package/dist/application/AnalysisClientCleanup.js +14 -0
- package/dist/application/ApplicationWorkflowEvidenceResolver.js +31 -13
- package/dist/application/BinarySession.js +15 -4
- package/dist/application/BinarySessionStatus.js +31 -1
- package/dist/application/BrowserEvidence.js +14 -0
- package/dist/application/BrowserScenarioCapturePort.js +1 -0
- package/dist/application/BrowserScenarioCaptureService.js +53 -0
- package/dist/application/BrowserScenarioEvidence.js +40 -0
- package/dist/application/CallPathTracing.js +160 -0
- package/dist/application/CapabilityInventory.js +68 -12
- package/dist/application/CompositeProvider.js +10 -1
- package/dist/application/DirectAnalysis.js +7 -2
- package/dist/application/EnhancedLiteralTracing.js +118 -0
- package/dist/application/EnhancedTools.js +35 -121
- package/dist/application/JavaScriptApplicationWorkflowEvidence.js +13 -0
- package/dist/application/JavaScriptApplicationWorkflowService.js +27 -1
- package/dist/application/JavaScriptRuntimeObservationEvidence.js +34 -0
- package/dist/application/JavaScriptRuntimeObservationPort.js +1 -0
- package/dist/application/JavaScriptRuntimeObservationService.js +46 -0
- package/dist/application/JavaScriptSemanticGraphAsyncProjection.js +147 -0
- package/dist/application/JavaScriptSemanticGraphBuilder.js +34 -6
- package/dist/application/JavaScriptSemanticGraphChildProcessProjection.js +156 -0
- package/dist/application/JavaScriptSemanticGraphConstruction.js +2 -2
- package/dist/application/JavaScriptSemanticGraphDataProjection.js +203 -0
- package/dist/application/JavaScriptSemanticGraphFingerprintProjection.js +25 -0
- package/dist/application/JavaScriptSemanticGraphFlowProjection.js +129 -8
- package/dist/application/JavaScriptSemanticGraphObjectProjection.js +81 -0
- package/dist/application/JavaScriptSemanticGraphProjection.js +14 -7
- package/dist/application/JavaScriptSemanticGraphResourceProjection.js +59 -0
- package/dist/application/JavaScriptSemanticGraphValueProjection.js +77 -0
- package/dist/application/ProcessReactiveEffects.js +6 -0
- package/dist/application/ProcessReactiveHarness.js +16 -1
- package/dist/application/ProjectPermissionStore.js +1 -0
- package/dist/application/ReconstructionObligationAuthority.js +72 -0
- package/dist/application/ReconstructionObligationCandidates.js +265 -0
- package/dist/application/ReconstructionObligationLedgerDiagnostics.js +47 -0
- package/dist/application/ReconstructionObligationLedgerEvaluation.js +263 -0
- package/dist/application/ReconstructionObligationLedgerProjection.js +86 -0
- package/dist/application/ReconstructionObligationLedgerService.js +72 -0
- package/dist/application/ReconstructionObligationOriginalCases.js +33 -0
- package/dist/application/ReconstructionObligationPolicies.js +132 -0
- package/dist/application/ReconstructionReadinessService.js +48 -0
- package/dist/artifacts/ArtifactProvider.js +87 -22
- package/dist/browser/BrowserScenarioSecrets.js +53 -0
- package/dist/browser/BrowserScenarioSessionPort.js +1 -0
- package/dist/browser/CdpBrowserProvider.js +2 -2
- package/dist/browser/CdpCaptureDocuments.js +57 -0
- package/dist/browser/CdpCaptureStorage.js +71 -29
- package/dist/browser/CdpCaptureStorageFingerprints.js +328 -0
- package/dist/browser/CdpEndpoint.js +8 -6
- package/dist/browser/CdpPageCapture.js +2 -0
- package/dist/browser/JavaScriptRuntimeScope.js +38 -0
- package/dist/browser/PlaywrightBrowserScenarioProvider.js +253 -0
- package/dist/browser/PlaywrightExecutionBoundary.js +42 -0
- package/dist/browser/PlaywrightScenarioActions.js +62 -0
- package/dist/browser/PlaywrightScenarioArtifacts.js +238 -0
- package/dist/browser/PlaywrightScenarioBrowser.js +144 -0
- package/dist/browser/PlaywrightScenarioEvents.js +301 -0
- package/dist/browser/PlaywrightScenarioSession.js +300 -0
- package/dist/browser/V8InspectorCaptureProjection.js +110 -0
- package/dist/browser/V8InspectorEndpoint.js +72 -0
- package/dist/browser/V8InspectorProvider.js +267 -0
- package/dist/catalogIdentity.js +8 -0
- package/dist/cli/artifactCommands.js +39 -0
- package/dist/cli/coreAnalysisCommands.js +37 -0
- package/dist/cli.js +4 -0
- package/dist/cliApplicationCommands.js +30 -2
- package/dist/cliBrowserAdvancedCommands.js +17 -5
- package/dist/cliBrowserCommands.js +1 -0
- package/dist/cliBrowserScenarioCommands.js +43 -0
- package/dist/cliCommandNames.js +8 -0
- package/dist/cliJavaScriptRuntimeCommands.js +164 -0
- package/dist/cliObservationOptions.js +4 -0
- package/dist/cliPolicyCommands.js +1 -0
- package/dist/config/environment.js +12 -0
- package/dist/config/parseConfig.js +88 -17
- package/dist/contracts/analysisSearchContract.js +14 -0
- package/dist/contracts/applicationToolContracts.js +86 -2
- package/dist/contracts/applicationWorkflowInputContracts.js +18 -0
- package/dist/contracts/artifactToolContracts.js +22 -0
- package/dist/contracts/browserProviderToolContracts.js +7 -0
- package/dist/contracts/browserScenarioToolContracts.js +81 -0
- package/dist/contracts/browserToolContracts.js +88 -98
- package/dist/contracts/enhancedInputs.js +25 -6
- package/dist/contracts/functionInstructionContract.js +8 -0
- package/dist/contracts/hopperMemoryContracts.js +22 -0
- package/dist/contracts/javascriptApplicationWorkflowExamples.js +42 -0
- package/dist/contracts/javascriptRuntimeObservationToolContracts.js +58 -0
- package/dist/contracts/reconstructionReadinessExample.js +436 -0
- package/dist/contracts/toolContractExamples.js +5 -0
- package/dist/contracts/toolContractTypes.js +1 -0
- package/dist/contracts/toolContracts.js +13 -21
- package/dist/contracts/toolEffects.js +21 -0
- package/dist/contracts/toolOutputSchemaGroups.js +70 -20
- package/dist/contracts/toolOutputSchemaPrimitives.js +37 -0
- package/dist/domain/analysisErrorProjection.js +8 -0
- package/dist/domain/artifactInspection.js +347 -0
- package/dist/domain/browserCaptureComparison.js +82 -0
- package/dist/domain/browserObservation.js +8 -1
- package/dist/domain/browserObservationErrors.js +1 -0
- package/dist/domain/browserObservationSchemas.js +26 -0
- package/dist/domain/browserScenario.js +165 -0
- package/dist/domain/browserScenarioCapture.js +57 -0
- package/dist/domain/browserScenarioCaptureValues.js +204 -0
- package/dist/domain/browserScenarioDiff.js +324 -0
- package/dist/domain/browserScenarioDiffValues.js +157 -0
- package/dist/domain/browserScenarioNormalization.js +45 -0
- package/dist/domain/browserScenarioValues.js +372 -0
- package/dist/domain/hopperValues.js +10 -0
- package/dist/domain/javascriptRuntimeObservation.js +170 -0
- package/dist/domain/javascriptRuntimeReconciliationParsing.js +120 -2
- package/dist/domain/javascriptRuntimeReconciliationResult.js +3 -3
- package/dist/domain/javascriptRuntimeReconciliationRuntime.js +2 -2
- package/dist/domain/javascriptRuntimeReconciliationSchemas.js +1 -1
- package/dist/domain/javascriptSemanticAnalysis.js +5 -4
- package/dist/domain/javascriptSemanticAsyncEffects.js +253 -0
- package/dist/domain/javascriptSemanticChildProcesses.js +237 -0
- package/dist/domain/javascriptSemanticDataEffectHelpers.js +125 -0
- package/dist/domain/javascriptSemanticDataEffects.js +284 -0
- package/dist/domain/javascriptSemanticDerivedAnalysis.js +41 -0
- package/dist/domain/javascriptSemanticFingerprints.js +174 -0
- package/dist/domain/javascriptSemanticIr.js +21 -1
- package/dist/domain/javascriptSemanticObjects.js +115 -0
- package/dist/domain/javascriptSemanticPromises.js +290 -0
- package/dist/domain/javascriptSemanticResources.js +136 -0
- package/dist/domain/javascriptStaticAnalysis.js +4 -3
- package/dist/domain/javascriptStaticAnalysisBundler.js +38 -0
- package/dist/domain/javascriptStaticAnalysisCalls.js +23 -1
- package/dist/domain/javascriptStaticAnalysisHelpers.js +8 -0
- package/dist/domain/processCapture.js +8 -2
- package/dist/domain/processReactiveScenario.js +1 -0
- package/dist/domain/processReactiveTransition.js +9 -4
- package/dist/domain/providerCleanupError.js +11 -0
- package/dist/domain/reconstructionObligationLedgerSchemas.js +278 -0
- package/dist/domain/reconstructionReadiness.js +121 -0
- package/dist/domain/reconstructionReadinessFindingHelpers.js +17 -0
- package/dist/domain/reconstructionReadinessIntegrityFindings.js +118 -0
- package/dist/domain/reconstructionReadinessJourneyFindings.js +193 -0
- package/dist/domain/reconstructionReadinessSchemas.js +240 -0
- package/dist/domain/sourceToBundleComparison.js +335 -0
- package/dist/domain/sourceToBundleComparisonSchemas.js +133 -0
- package/dist/domain/sourceToBundleSignals.js +202 -0
- package/dist/domain/webCaptureDiff.js +141 -27
- package/dist/domain/webScreenshot.js +1 -1
- package/dist/generatedPackageMetadata.js +1 -1
- package/dist/ghidra/GhidraDefaults.js +1 -1
- package/dist/ghidra/GhidraFunctionValues.js +13 -1
- package/dist/ghidra/GhidraProviderCapabilities.js +7 -0
- package/dist/hopper/HopperCleanup.js +158 -0
- package/dist/hopper/HopperClient.js +69 -99
- package/dist/hopper/HopperProvider.js +20 -1
- package/dist/hopper/HopperRequestQueue.js +187 -0
- package/dist/hopper/HopperResponseStream.js +46 -0
- package/dist/hopper/protocol.js +1 -0
- package/dist/main/transport.js +24 -24
- package/dist/main.js +6 -0
- package/dist/mcpDoctor.js +25 -2
- package/dist/process/ProcessCommandIdentity.js +19 -0
- package/dist/process/ProcessOwnership.js +11 -8
- package/dist/server/DynamicToolAvailability.js +70 -0
- package/dist/server/createServer.js +46 -1
- package/dist/server/lifecycleProgress.js +10 -0
- package/dist/server/permissionFailure.js +12 -0
- package/dist/server/registerApplicationTools/compareSourceToBundle.js +35 -0
- package/dist/server/registerApplicationTools/helpers.js +11 -7
- package/dist/server/registerApplicationTools/obligationLedger.js +27 -0
- package/dist/server/registerApplicationTools/readiness.js +55 -0
- package/dist/server/registerApplicationTools.js +6 -0
- package/dist/server/registerArtifactTools.js +9 -1
- package/dist/server/registerBrowserScenarioTool.js +25 -0
- package/dist/server/registerBrowserTools.js +2 -2
- package/dist/server/registerCloseLifecycleTool.js +57 -0
- package/dist/server/registerEnhancedTools.js +16 -5
- package/dist/server/registerEvidenceResources.js +4 -0
- package/dist/server/registerEvidenceTools.js +9 -2
- package/dist/server/registerJavaScriptRuntimeObservationTools.js +37 -0
- package/dist/server/registerReconstructionObligationLedgerResource.js +39 -0
- package/dist/server/registerReconstructionReadinessResource.js +39 -0
- package/dist/server/registerSessionStatusTool.js +11 -8
- package/dist/server/registerSessionTools.js +11 -57
- package/dist/server/sessionAvailabilityPolicy.js +10 -8
- package/package.json +12 -6
- package/skills/reverse-engineer-anything/SKILL.md +2 -2
package/bridge/hopper_bridge.py
CHANGED
|
@@ -11,19 +11,86 @@ import os
|
|
|
11
11
|
import re
|
|
12
12
|
import sre_parse
|
|
13
13
|
import socket
|
|
14
|
+
from typing import Any, Optional, Protocol, Sequence
|
|
14
15
|
|
|
15
16
|
MAX_LINE_BYTES = 10 * 1024 * 1024
|
|
17
|
+
MAX_READ_BYTES = 4096
|
|
16
18
|
BAD_ADDRESSES = (-1, 0xFFFFFFFFFFFFFFFF, None)
|
|
17
19
|
_selected_document = None
|
|
18
20
|
_search_inventory_cache = {}
|
|
21
|
+
_pseudocode_cache = {}
|
|
22
|
+
_hopper_api = None
|
|
19
23
|
MAX_SEARCH_PATTERN_LENGTH = 256
|
|
20
24
|
MAX_SEARCH_VALUE_LENGTH = 4096
|
|
21
25
|
|
|
22
26
|
|
|
27
|
+
class HopperDocument(Protocol):
|
|
28
|
+
"""Typed minimum used by the provider-neutral Hopper API facade."""
|
|
29
|
+
|
|
30
|
+
def getDocumentName(self) -> str: ...
|
|
31
|
+
|
|
32
|
+
def getExecutableFilePath(self) -> Optional[str]: ...
|
|
33
|
+
|
|
34
|
+
def getDatabaseFilePath(self) -> Optional[str]: ...
|
|
35
|
+
|
|
36
|
+
def backgroundProcessActive(self) -> bool: ...
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class HopperDocumentProvider(Protocol):
|
|
40
|
+
"""Hopper's injected global document provider."""
|
|
41
|
+
|
|
42
|
+
@classmethod
|
|
43
|
+
def getAllDocuments(cls) -> Sequence[HopperDocument]: ...
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def getCurrentDocument(cls) -> Optional[HopperDocument]: ...
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class HopperApiFacade:
|
|
50
|
+
"""Small injected boundary around Hopper globals for standalone tests."""
|
|
51
|
+
|
|
52
|
+
def __init__(self, document_provider: HopperDocumentProvider):
|
|
53
|
+
self._document_provider = document_provider
|
|
54
|
+
|
|
55
|
+
def documents(self) -> Sequence[HopperDocument]:
|
|
56
|
+
return self._document_provider.getAllDocuments()
|
|
57
|
+
|
|
58
|
+
def current_document(self) -> Optional[HopperDocument]:
|
|
59
|
+
return self._document_provider.getCurrentDocument()
|
|
60
|
+
|
|
61
|
+
def require_analysis_complete(
|
|
62
|
+
self, document: HopperDocument, operation: str
|
|
63
|
+
) -> None:
|
|
64
|
+
if document.backgroundProcessActive():
|
|
65
|
+
raise CapabilityUnavailableError(
|
|
66
|
+
"%s requires completed Hopper background analysis" % operation
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _configure_hopper_api(document_provider):
|
|
71
|
+
"""Inject Hopper's document provider or a standalone test facade."""
|
|
72
|
+
global _hopper_api
|
|
73
|
+
_hopper_api = HopperApiFacade(document_provider)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _api():
|
|
77
|
+
"""Resolve Hopper globals lazily so importing this module needs no Hopper."""
|
|
78
|
+
global _hopper_api
|
|
79
|
+
if _hopper_api is not None:
|
|
80
|
+
return _hopper_api
|
|
81
|
+
document_provider = globals().get("Document")
|
|
82
|
+
if document_provider is None:
|
|
83
|
+
raise CapabilityUnavailableError(
|
|
84
|
+
"Hopper Document API is unavailable outside the Hopper runtime"
|
|
85
|
+
)
|
|
86
|
+
_hopper_api = HopperApiFacade(document_provider)
|
|
87
|
+
return _hopper_api
|
|
88
|
+
|
|
89
|
+
|
|
23
90
|
def _session_document():
|
|
24
91
|
"""Find only the document opened for this authenticated REA session."""
|
|
25
92
|
target = os.path.realpath(REA_TARGET_PATH)
|
|
26
|
-
for document in
|
|
93
|
+
for document in _api().documents():
|
|
27
94
|
paths = (document.getExecutableFilePath(), document.getDatabaseFilePath())
|
|
28
95
|
for path in paths:
|
|
29
96
|
if path and os.path.realpath(path) == target:
|
|
@@ -36,6 +103,31 @@ MAX_REGEX_CANDIDATE_LENGTH = 4096
|
|
|
36
103
|
MAX_REGEX_SEARCH_WORK_UNITS = 1000000
|
|
37
104
|
|
|
38
105
|
|
|
106
|
+
class CapabilityUnavailableError(Exception):
|
|
107
|
+
"""The active Hopper build lacks one admitted public API operation."""
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
EXHAUSTIVE_ANALYSIS_METHODS = frozenset(
|
|
111
|
+
(
|
|
112
|
+
"analyze_function",
|
|
113
|
+
"list_names",
|
|
114
|
+
"list_procedures",
|
|
115
|
+
"list_strings",
|
|
116
|
+
"procedure_address",
|
|
117
|
+
"procedure_assembly",
|
|
118
|
+
"procedure_callees",
|
|
119
|
+
"procedure_callers",
|
|
120
|
+
"procedure_info",
|
|
121
|
+
"procedure_pseudo_code",
|
|
122
|
+
"procedure_references",
|
|
123
|
+
"read_function_instructions",
|
|
124
|
+
"resolve_containing_procedure",
|
|
125
|
+
"search_procedures",
|
|
126
|
+
"search_strings",
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
|
|
39
131
|
def _hex(value):
|
|
40
132
|
return "0x%x" % value
|
|
41
133
|
|
|
@@ -54,7 +146,8 @@ def _json_safe(value):
|
|
|
54
146
|
def _document(name=None):
|
|
55
147
|
"""Resolve an explicit or session-selected document without changing Hopper UI."""
|
|
56
148
|
global _selected_document
|
|
57
|
-
|
|
149
|
+
api = _api()
|
|
150
|
+
documents = api.documents()
|
|
58
151
|
if name is not None:
|
|
59
152
|
for candidate in documents:
|
|
60
153
|
if candidate.getDocumentName() == name:
|
|
@@ -64,7 +157,7 @@ def _document(name=None):
|
|
|
64
157
|
for candidate in documents:
|
|
65
158
|
if candidate.getDocumentName() == _selected_document:
|
|
66
159
|
return candidate
|
|
67
|
-
current =
|
|
160
|
+
current = api.current_document()
|
|
68
161
|
if current is None:
|
|
69
162
|
raise ValueError("No Hopper document is loaded")
|
|
70
163
|
return current
|
|
@@ -225,12 +318,35 @@ def _invalidate_search_inventory(document):
|
|
|
225
318
|
del _search_inventory_cache[key]
|
|
226
319
|
|
|
227
320
|
|
|
321
|
+
def _invalidate_pseudocode(document):
|
|
322
|
+
"""Discard cached decompilation after an analysis annotation changes."""
|
|
323
|
+
document_id = id(document)
|
|
324
|
+
for key in list(_pseudocode_cache):
|
|
325
|
+
if key[0] == document_id:
|
|
326
|
+
del _pseudocode_cache[key]
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def _pseudocode(document, procedure):
|
|
330
|
+
"""Reuse one provider decompilation for repeated bounded projections."""
|
|
331
|
+
key = (id(document), procedure.getEntryPoint())
|
|
332
|
+
if key not in _pseudocode_cache:
|
|
333
|
+
_pseudocode_cache[key] = procedure.decompile()
|
|
334
|
+
return _pseudocode_cache[key]
|
|
335
|
+
|
|
336
|
+
|
|
228
337
|
def _search_inventory(document, kind):
|
|
229
338
|
"""Cache an immutable, address-sorted inventory for an unchanged document."""
|
|
230
339
|
key = (id(document), kind)
|
|
231
340
|
inventory = _search_inventory_cache.get(key)
|
|
232
341
|
if inventory is None:
|
|
233
|
-
|
|
342
|
+
if kind == "procedure":
|
|
343
|
+
values = _procedure_map(document)
|
|
344
|
+
elif kind == "string":
|
|
345
|
+
values = _strings(document)
|
|
346
|
+
elif kind == "name":
|
|
347
|
+
values = {_hex(address): value for address, value in _name_map(document).items()}
|
|
348
|
+
else:
|
|
349
|
+
raise ValueError("Unknown inventory kind")
|
|
234
350
|
inventory = tuple(sorted(values.items(), key=lambda item: int(item[0], 16)))
|
|
235
351
|
_search_inventory_cache[key] = inventory
|
|
236
352
|
return inventory
|
|
@@ -507,6 +623,22 @@ def _name_map(document):
|
|
|
507
623
|
return result
|
|
508
624
|
|
|
509
625
|
|
|
626
|
+
def _render_instruction(segment, address):
|
|
627
|
+
instruction = segment.getInstructionAtAddress(address)
|
|
628
|
+
if instruction is None:
|
|
629
|
+
return None
|
|
630
|
+
arguments = [
|
|
631
|
+
instruction.getFormattedArgument(index)
|
|
632
|
+
for index in range(instruction.getArgumentCount())
|
|
633
|
+
]
|
|
634
|
+
suffix = ", ".join(value for value in arguments if value is not None)
|
|
635
|
+
return "%s: %s%s" % (
|
|
636
|
+
_hex(address),
|
|
637
|
+
instruction.getInstructionString(),
|
|
638
|
+
(" " + suffix) if suffix else "",
|
|
639
|
+
)
|
|
640
|
+
|
|
641
|
+
|
|
510
642
|
def _assembly(procedure, limit=None):
|
|
511
643
|
"""Render bounded assembly while guarding against malformed instruction cycles."""
|
|
512
644
|
lines = []
|
|
@@ -520,9 +652,9 @@ def _assembly(procedure, limit=None):
|
|
|
520
652
|
instruction = segment.getInstructionAtAddress(address)
|
|
521
653
|
if instruction is None:
|
|
522
654
|
break
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
655
|
+
line = _render_instruction(segment, address)
|
|
656
|
+
if line is not None:
|
|
657
|
+
lines.append(line)
|
|
526
658
|
if limit is not None and len(lines) >= limit:
|
|
527
659
|
return "\n".join(lines)
|
|
528
660
|
length = instruction.getInstructionLength()
|
|
@@ -532,6 +664,45 @@ def _assembly(procedure, limit=None):
|
|
|
532
664
|
return "\n".join(lines)
|
|
533
665
|
|
|
534
666
|
|
|
667
|
+
def _read_function_instructions(document, params):
|
|
668
|
+
"""Read one bounded raw-instruction window without invoking decompilation."""
|
|
669
|
+
procedure = _procedure(document, params.get("procedure"))
|
|
670
|
+
offset = _offset(params, "offset")
|
|
671
|
+
limit = params.get("limit", 64)
|
|
672
|
+
if offset > 100000:
|
|
673
|
+
raise ValueError("offset must not exceed 100000")
|
|
674
|
+
if not isinstance(limit, int) or isinstance(limit, bool) or limit < 1 or limit > 500:
|
|
675
|
+
raise ValueError("limit must be an integer between 1 and 500")
|
|
676
|
+
addresses, scan_truncated = _instruction_addresses(
|
|
677
|
+
procedure, offset + limit + 1
|
|
678
|
+
)
|
|
679
|
+
start = min(offset, len(addresses))
|
|
680
|
+
end = min(len(addresses), start + limit)
|
|
681
|
+
segment = procedure.getSegment()
|
|
682
|
+
items = []
|
|
683
|
+
for address in addresses[start:end]:
|
|
684
|
+
line = _render_instruction(segment, address)
|
|
685
|
+
if line is not None:
|
|
686
|
+
items.append(line)
|
|
687
|
+
has_more = scan_truncated or end < len(addresses)
|
|
688
|
+
return {
|
|
689
|
+
"procedure": _procedure_identity(procedure),
|
|
690
|
+
"instructions": {
|
|
691
|
+
"items": items,
|
|
692
|
+
"total": None if scan_truncated else len(addresses),
|
|
693
|
+
"returned": len(items),
|
|
694
|
+
"truncated": has_more,
|
|
695
|
+
"next_offset": end if has_more else None,
|
|
696
|
+
},
|
|
697
|
+
"instructions_scanned": len(addresses),
|
|
698
|
+
"instruction_scan_truncated": scan_truncated,
|
|
699
|
+
"limitations": [
|
|
700
|
+
"Instruction text and ordering are Hopper-specific representations.",
|
|
701
|
+
"The fast path does not decompile the procedure or scan whole-program names and strings.",
|
|
702
|
+
],
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
|
|
535
706
|
def _analyze_function(document, params):
|
|
536
707
|
"""Collect a bounded, single-pass function dossier for agent callers."""
|
|
537
708
|
procedure = _procedure(document, params.get("procedure"))
|
|
@@ -560,8 +731,8 @@ def _analyze_function(document, params):
|
|
|
560
731
|
"end": _hex(block.getEndingAddress()),
|
|
561
732
|
"successors": sorted(set(successors), key=lambda value: int(value, 16)),
|
|
562
733
|
})
|
|
563
|
-
pseudo = procedure
|
|
564
|
-
assembly_lines = _assembly(procedure).splitlines() if params.get("include_assembly", False) else []
|
|
734
|
+
pseudo = _pseudocode(document, procedure) or ""
|
|
735
|
+
assembly_lines = _assembly(procedure, max_instructions).splitlines() if params.get("include_assembly", False) else []
|
|
565
736
|
callers = sorted((_procedure_identity(item) for item in procedure.getAllCallerProcedures()), key=lambda item: int(item["address"], 16))
|
|
566
737
|
callees = sorted((_procedure_identity(item) for item in procedure.getAllCalleeProcedures()), key=lambda item: int(item["address"], 16))
|
|
567
738
|
comments = []
|
|
@@ -595,8 +766,14 @@ def _analyze_function(document, params):
|
|
|
595
766
|
incoming.append(item)
|
|
596
767
|
if source in procedure_addresses:
|
|
597
768
|
outgoing.append(item)
|
|
598
|
-
string_map = {
|
|
599
|
-
|
|
769
|
+
string_map = {
|
|
770
|
+
int(address, 16): value
|
|
771
|
+
for address, value in _search_inventory(document, "string")
|
|
772
|
+
}
|
|
773
|
+
name_map = {
|
|
774
|
+
int(address, 16): value
|
|
775
|
+
for address, value in _search_inventory(document, "name")
|
|
776
|
+
}
|
|
600
777
|
referenced_strings = []
|
|
601
778
|
referenced_names = []
|
|
602
779
|
for edge in outgoing:
|
|
@@ -615,7 +792,13 @@ def _analyze_function(document, params):
|
|
|
615
792
|
return {
|
|
616
793
|
"procedure": {"address": _hex(procedure.getEntryPoint()), "name": _procedure_name(procedure), "classification": None, "signature": procedure.signatureString(), "locals": _procedure_locals(procedure)},
|
|
617
794
|
"pseudocode": {"text": pseudo_text, "total_chars": len(pseudo), "returned_chars": len(pseudo_text), "truncated": pseudo_next < len(pseudo), "next_offset": pseudo_next if pseudo_next < len(pseudo) else None},
|
|
618
|
-
"assembly": _bounded(
|
|
795
|
+
"assembly": _bounded(
|
|
796
|
+
assembly_lines,
|
|
797
|
+
assembly_offset,
|
|
798
|
+
max_instructions,
|
|
799
|
+
None,
|
|
800
|
+
instruction_scan_truncated if params.get("include_assembly", False) else False,
|
|
801
|
+
),
|
|
619
802
|
"comments": collection("comments", comments, instruction_scan_truncated),
|
|
620
803
|
"callers": collection("callers", callers), "callees": collection("callees", callees),
|
|
621
804
|
"incoming_references": collection("incoming_references", incoming, instruction_scan_truncated),
|
|
@@ -671,9 +854,54 @@ def _dispatch(method, params):
|
|
|
671
854
|
return _selected_document
|
|
672
855
|
|
|
673
856
|
document = _document(params.get("document"))
|
|
857
|
+
if method in EXHAUSTIVE_ANALYSIS_METHODS:
|
|
858
|
+
_api().require_analysis_complete(document, method)
|
|
674
859
|
|
|
675
860
|
if method == "analyze_function":
|
|
676
861
|
return _analyze_function(document, params)
|
|
862
|
+
if method == "read_function_instructions":
|
|
863
|
+
return _read_function_instructions(document, params)
|
|
864
|
+
if method == "read_bytes":
|
|
865
|
+
length = params.get("length", 256)
|
|
866
|
+
if isinstance(length, bool) or not isinstance(length, int):
|
|
867
|
+
raise ValueError("Byte-read length must be an integer")
|
|
868
|
+
if length < 1 or length > MAX_READ_BYTES:
|
|
869
|
+
raise ValueError("Byte-read length must be between 1 and 4096")
|
|
870
|
+
reader = getattr(document, "readBytes", None)
|
|
871
|
+
if not callable(reader):
|
|
872
|
+
raise CapabilityUnavailableError(
|
|
873
|
+
"Hopper's public Python API does not expose readBytes"
|
|
874
|
+
)
|
|
875
|
+
address = _address(document, params.get("address"))
|
|
876
|
+
value = reader(address, length)
|
|
877
|
+
if not isinstance(value, (bytes, bytearray)):
|
|
878
|
+
raise CapabilityUnavailableError(
|
|
879
|
+
"Hopper's public Python API returned an unsupported byte representation"
|
|
880
|
+
)
|
|
881
|
+
data = bytes(value)
|
|
882
|
+
return {
|
|
883
|
+
"address": _hex(address),
|
|
884
|
+
"requested_bytes": length,
|
|
885
|
+
"returned_bytes": len(data),
|
|
886
|
+
"bytes_hex": data.hex(),
|
|
887
|
+
"complete": len(data) == length,
|
|
888
|
+
}
|
|
889
|
+
if method == "address_to_file_offset":
|
|
890
|
+
mapper = getattr(document, "getFileOffsetFromAddress", None)
|
|
891
|
+
if not callable(mapper):
|
|
892
|
+
raise CapabilityUnavailableError(
|
|
893
|
+
"Hopper's public Python API does not expose getFileOffsetFromAddress"
|
|
894
|
+
)
|
|
895
|
+
address = _address(document, params.get("address"))
|
|
896
|
+
offset = mapper(address)
|
|
897
|
+
if (
|
|
898
|
+
isinstance(offset, bool)
|
|
899
|
+
or not isinstance(offset, int)
|
|
900
|
+
or offset in BAD_ADDRESSES
|
|
901
|
+
or offset < 0
|
|
902
|
+
):
|
|
903
|
+
raise ValueError("Address has no authoritative file-offset mapping")
|
|
904
|
+
return {"address": _hex(address), "file_offset": offset}
|
|
677
905
|
if method == "resolve_containing_procedure":
|
|
678
906
|
address = _address(document, params.get("address"))
|
|
679
907
|
procedure, reason = _containing_procedure(document, address)
|
|
@@ -740,19 +968,16 @@ def _dispatch(method, params):
|
|
|
740
968
|
})
|
|
741
969
|
return result
|
|
742
970
|
if method == "list_procedures":
|
|
743
|
-
return _page(
|
|
971
|
+
return _page(dict(_search_inventory(document, "procedure")), params.get("offset", 0), params.get("limit", 100))
|
|
744
972
|
if method == "list_strings":
|
|
745
|
-
values =
|
|
973
|
+
values = dict(_search_inventory(document, "string"))
|
|
746
974
|
requested = params.get("address")
|
|
747
975
|
if requested is not None:
|
|
748
976
|
key = _hex(_address(document, requested))
|
|
749
977
|
values = {key: values[key]} if key in values else {}
|
|
750
978
|
return _page(values, params.get("offset", 0), params.get("limit", 100))
|
|
751
979
|
if method == "list_names":
|
|
752
|
-
result =
|
|
753
|
-
for segment in document.getSegmentsList():
|
|
754
|
-
for item in segment.getNamedAddresses():
|
|
755
|
-
result[_hex(item)] = segment.getNameAtAddress(item)
|
|
980
|
+
result = dict(_search_inventory(document, "name"))
|
|
756
981
|
requested = params.get("address")
|
|
757
982
|
if requested is not None:
|
|
758
983
|
key = _hex(_address(document, requested))
|
|
@@ -768,7 +993,7 @@ def _dispatch(method, params):
|
|
|
768
993
|
if method == "procedure_assembly":
|
|
769
994
|
return _assembly(procedure)
|
|
770
995
|
if method == "procedure_pseudo_code":
|
|
771
|
-
return procedure
|
|
996
|
+
return _pseudocode(document, procedure)
|
|
772
997
|
if method == "procedure_callers":
|
|
773
998
|
return sorted((_hex(item.getEntryPoint()) for item in procedure.getAllCallerProcedures()), key=lambda value: int(value, 16))
|
|
774
999
|
if method == "procedure_callees":
|
|
@@ -779,12 +1004,18 @@ def _dispatch(method, params):
|
|
|
779
1004
|
return {"name": _procedure_name(procedure), "entrypoint": _hex(procedure.getEntryPoint()), "basicblock_count": procedure.getBasicBlockCount(), "length": length, "signature": procedure.signatureString(), "locals": _procedure_locals(procedure), "classification": None}
|
|
780
1005
|
if method == "set_address_name":
|
|
781
1006
|
address = _address(document, params.get("address"))
|
|
782
|
-
|
|
783
|
-
|
|
1007
|
+
try:
|
|
1008
|
+
result = document.setNameAtAddress(address, params["name"])
|
|
1009
|
+
finally:
|
|
1010
|
+
_invalidate_search_inventory(document)
|
|
1011
|
+
_invalidate_pseudocode(document)
|
|
784
1012
|
return result
|
|
785
1013
|
if method == "set_addresses_names":
|
|
786
|
-
|
|
787
|
-
|
|
1014
|
+
try:
|
|
1015
|
+
result = {key: document.setNameAtAddress(_address(document, key), value) for key, value in params["names"].items()}
|
|
1016
|
+
finally:
|
|
1017
|
+
_invalidate_search_inventory(document)
|
|
1018
|
+
_invalidate_pseudocode(document)
|
|
788
1019
|
return result
|
|
789
1020
|
if method in ("set_comment", "set_inline_comment"):
|
|
790
1021
|
address = _address(document, params.get("address"))
|
|
@@ -792,6 +1023,7 @@ def _dispatch(method, params):
|
|
|
792
1023
|
setter = segment.setCommentAtAddress if method == "set_comment" else segment.setInlineCommentAtAddress
|
|
793
1024
|
getter = segment.getCommentAtAddress if method == "set_comment" else segment.getInlineCommentAtAddress
|
|
794
1025
|
setter(address, params["comment"])
|
|
1026
|
+
_invalidate_pseudocode(document)
|
|
795
1027
|
return getter(address) == params["comment"]
|
|
796
1028
|
if method == "list_bookmarks":
|
|
797
1029
|
return [{"address": _hex(item), "name": document.getBookmarkName(item)} for item in document.getBookmarks()]
|
|
@@ -838,6 +1070,8 @@ def _serve_connection(connection):
|
|
|
838
1070
|
|
|
839
1071
|
|
|
840
1072
|
def _diagnostic_type(error):
|
|
1073
|
+
if isinstance(error, CapabilityUnavailableError):
|
|
1074
|
+
return "capability_unavailable"
|
|
841
1075
|
if isinstance(error, PermissionError):
|
|
842
1076
|
return "authorization"
|
|
843
1077
|
if isinstance(error, (ValueError, TypeError, KeyError)):
|
|
@@ -864,4 +1098,5 @@ def _run():
|
|
|
864
1098
|
|
|
865
1099
|
# Hopper's public objects are bound to its dedicated Python execution thread.
|
|
866
1100
|
# Keep dispatch on that thread; moving calls to an arbitrary worker can deadlock.
|
|
867
|
-
|
|
1101
|
+
if __name__ == "__main__":
|
|
1102
|
+
_run()
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ProviderCleanupError } from "../domain/providerCleanupError.js";
|
|
2
|
+
import { err, ok } from "../domain/result.js";
|
|
3
|
+
/** Close one provider client and normalize unexpected adapter rejection. */
|
|
4
|
+
export const closeAnalysisClient = async (client, providerId, options = {}) => {
|
|
5
|
+
try {
|
|
6
|
+
if (client.closeWithOutcome !== undefined)
|
|
7
|
+
return await client.closeWithOutcome(options);
|
|
8
|
+
await client.close();
|
|
9
|
+
return ok(null);
|
|
10
|
+
}
|
|
11
|
+
catch (cause) {
|
|
12
|
+
return err(new ProviderCleanupError(providerId, ["provider-client"], { reason: "provider client close rejected unexpectedly" }, { cause }));
|
|
13
|
+
}
|
|
14
|
+
};
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { compareApplicationVersionsRequestSchema, compareJavaScriptExportShapesRequestSchema, traceApplicationFeatureRequestSchema, traceJavaScriptSemanticsRequestSchema, } from "../contracts/applicationWorkflowInputContracts.js";
|
|
2
|
+
import { compareApplicationVersionsRequestSchema, compareJavaScriptExportShapesRequestSchema, compareSourceToBundleRequestSchema, traceApplicationFeatureRequestSchema, traceJavaScriptSemanticsRequestSchema, } from "../contracts/applicationWorkflowInputContracts.js";
|
|
3
3
|
import { AnalysisInputError } from "../domain/errors.js";
|
|
4
4
|
import { projectInputIssues } from "../domain/inputIssueProjection.js";
|
|
5
5
|
import { compareApplicationVersionsInputSchema } from "../domain/javascriptApplicationVersionComparisonSchemas.js";
|
|
6
6
|
import { compareJavaScriptExportShapesInputSchema } from "../domain/javascriptExportShapeComparisonSchemas.js";
|
|
7
7
|
import { traceApplicationFeatureInputSchema } from "../domain/javascriptFeatureTraceSchemas.js";
|
|
8
8
|
import { traceJavaScriptSemanticsInputSchema } from "../domain/javascriptSemanticTraceSchemas.js";
|
|
9
|
+
import { compareSourceToBundleInputSchema } from "../domain/sourceToBundleComparisonSchemas.js";
|
|
9
10
|
import { err, ok } from "../domain/result.js";
|
|
10
11
|
import { resolveEvidenceReferences, } from "./EvidenceReferenceResolver.js";
|
|
11
12
|
const APPLICATION_GRAPH_IDENTITIES = [
|
|
@@ -35,18 +36,14 @@ export const resolveTraceApplicationFeatureRequest = (input, lookup) => {
|
|
|
35
36
|
};
|
|
36
37
|
/** Resolve a trace request already parsed by the caller-facing contract. */
|
|
37
38
|
export const resolveTraceApplicationFeatureRequestValidated = (input, lookup) => {
|
|
38
|
-
const application = input.application
|
|
39
|
-
? resolveEvidenceReferences(lookup, input.application_evidence_id === undefined
|
|
40
|
-
? []
|
|
41
|
-
: [input.application_evidence_id], APPLICATION_GRAPH_IDENTITIES)
|
|
42
|
-
: ok([input.application]);
|
|
39
|
+
const application = graphEvidence(input.application, input.application_evidence_id, lookup);
|
|
43
40
|
if (!application.ok)
|
|
44
41
|
return application;
|
|
45
42
|
const native = resolveEvidenceReferences(lookup, input.native_observation_evidence_ids);
|
|
46
43
|
if (!native.ok)
|
|
47
44
|
return native;
|
|
48
45
|
const raw = {
|
|
49
|
-
application: application.value
|
|
46
|
+
application: application.value,
|
|
50
47
|
native_observations: [...input.native_observations, ...native.value],
|
|
51
48
|
seed: input.seed,
|
|
52
49
|
direction: input.direction,
|
|
@@ -66,14 +63,10 @@ export const resolveTraceJavaScriptSemanticsRequest = (input, lookup) => {
|
|
|
66
63
|
};
|
|
67
64
|
/** Resolve a semantic trace request already parsed by its public contract. */
|
|
68
65
|
export const resolveTraceJavaScriptSemanticsRequestValidated = (input, lookup) => {
|
|
69
|
-
const application = input.application
|
|
70
|
-
? resolveEvidenceReferences(lookup, input.application_evidence_id === undefined
|
|
71
|
-
? []
|
|
72
|
-
: [input.application_evidence_id], APPLICATION_GRAPH_IDENTITIES)
|
|
73
|
-
: ok([input.application]);
|
|
66
|
+
const application = graphEvidence(input.application, input.application_evidence_id, lookup);
|
|
74
67
|
if (!application.ok)
|
|
75
68
|
return application;
|
|
76
|
-
const raw = { application: application.value
|
|
69
|
+
const raw = { application: application.value, query: input.query };
|
|
77
70
|
const parsed = traceJavaScriptSemanticsInputSchema.safeParse(raw);
|
|
78
71
|
return parsed.success
|
|
79
72
|
? ok(parsed.data)
|
|
@@ -121,6 +114,31 @@ export const resolveCompareApplicationVersionsRequestValidated = (input, lookup)
|
|
|
121
114
|
? ok(parsed.data)
|
|
122
115
|
: invalid("compare_application_versions", parsed.error, raw);
|
|
123
116
|
};
|
|
117
|
+
/** Parse and resolve one historical-source comparison adapter request. */
|
|
118
|
+
export const resolveCompareSourceToBundleRequest = (input, lookup) => {
|
|
119
|
+
const parsed = compareSourceToBundleRequestSchema.safeParse(input);
|
|
120
|
+
return parsed.success
|
|
121
|
+
? resolveCompareSourceToBundleRequestValidated(parsed.data, lookup)
|
|
122
|
+
: invalid("compare_source_to_bundle", parsed.error, input);
|
|
123
|
+
};
|
|
124
|
+
/** Resolve a historical-source comparison request parsed by its public contract. */
|
|
125
|
+
export const resolveCompareSourceToBundleRequestValidated = (input, lookup) => {
|
|
126
|
+
const application = graphEvidence(input.application, input.application_evidence_id, lookup);
|
|
127
|
+
if (!application.ok)
|
|
128
|
+
return application;
|
|
129
|
+
const raw = {
|
|
130
|
+
reference: input.reference,
|
|
131
|
+
application: application.value,
|
|
132
|
+
limits: input.limits,
|
|
133
|
+
...(input.unknown_registry_approved === undefined
|
|
134
|
+
? {}
|
|
135
|
+
: { unknown_registry_approved: input.unknown_registry_approved }),
|
|
136
|
+
};
|
|
137
|
+
const parsed = compareSourceToBundleInputSchema.safeParse(raw);
|
|
138
|
+
return parsed.success
|
|
139
|
+
? ok(parsed.data)
|
|
140
|
+
: invalid("compare_source_to_bundle", parsed.error, raw);
|
|
141
|
+
};
|
|
124
142
|
/** Parse and resolve an export-shape adapter request into canonical input. */
|
|
125
143
|
export const resolveCompareJavaScriptExportShapesRequest = (input, lookup) => {
|
|
126
144
|
const parsed = compareJavaScriptExportShapesRequestSchema.safeParse(input);
|
|
@@ -9,6 +9,7 @@ import { BinarySessionRecords } from "./BinarySessionRecords.js";
|
|
|
9
9
|
import { binarySessionStatus } from "./BinarySessionStatus.js";
|
|
10
10
|
import { resolveSessionOpen, } from "./BinarySessionOpen.js";
|
|
11
11
|
import { prepareSessionExecution } from "./BinarySessionExecution.js";
|
|
12
|
+
import { closeAnalysisClient } from "./AnalysisClientCleanup.js";
|
|
12
13
|
const OFFICIAL_OPERATIONS = new Set(OFFICIAL_TOOL_CONTRACTS.map(({ name }) => name));
|
|
13
14
|
/**
|
|
14
15
|
* Owns the single active target shared by CLI and MCP adapters.
|
|
@@ -116,7 +117,14 @@ export class BinarySession extends BinarySessionRecords {
|
|
|
116
117
|
await this.#drainCalls();
|
|
117
118
|
const previous = this.#active;
|
|
118
119
|
this.#active = undefined;
|
|
119
|
-
|
|
120
|
+
if (previous !== undefined) {
|
|
121
|
+
const closed = await closeAnalysisClient(previous.client, previous.route.identity.id);
|
|
122
|
+
if (!closed.ok) {
|
|
123
|
+
this.clearSessionRecords();
|
|
124
|
+
this.#clearRuntimeAvailability();
|
|
125
|
+
return closed;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
120
128
|
const runId = randomUUID();
|
|
121
129
|
const client = route.createClient(target, { runId });
|
|
122
130
|
const started = await client.execute("health", {}, options);
|
|
@@ -157,15 +165,17 @@ export class BinarySession extends BinarySessionRecords {
|
|
|
157
165
|
});
|
|
158
166
|
}
|
|
159
167
|
/** Close the active target, if any. */
|
|
160
|
-
close() {
|
|
168
|
+
close(options = {}) {
|
|
161
169
|
return this.#serialize(async () => {
|
|
162
170
|
const previous = this.#active;
|
|
163
171
|
this.#active = undefined;
|
|
164
172
|
await this.#drainCalls();
|
|
165
|
-
|
|
173
|
+
const closed = previous === undefined
|
|
174
|
+
? ok(null)
|
|
175
|
+
: await closeAnalysisClient(previous.client, previous.route.identity.id, options);
|
|
166
176
|
this.clearSessionRecords();
|
|
167
177
|
this.#clearRuntimeAvailability();
|
|
168
|
-
return
|
|
178
|
+
return closed;
|
|
169
179
|
});
|
|
170
180
|
}
|
|
171
181
|
/** Describe the active binary session. */
|
|
@@ -177,6 +187,7 @@ export class BinarySession extends BinarySessionRecords {
|
|
|
177
187
|
runtimeAvailability: this.#runtimeAvailability,
|
|
178
188
|
runId: this.#active?.runId,
|
|
179
189
|
runtimeLineageSnapshots: this.#active?.client.runtimeLineageSnapshots?.() ?? [],
|
|
190
|
+
requestActivitySnapshots: this.#active?.client.requestActivitySnapshots?.() ?? [],
|
|
180
191
|
});
|
|
181
192
|
}
|
|
182
193
|
/** Return the immutable artifact identity captured before its provider started. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Project internal provider routing state into the caller-visible session status. */
|
|
2
|
-
export const binarySessionStatus = ({ target, route, router, runtimeAvailability, runId, runtimeLineageSnapshots, }) => {
|
|
2
|
+
export const binarySessionStatus = ({ target, route, router, runtimeAvailability, runId, runtimeLineageSnapshots, requestActivitySnapshots, }) => {
|
|
3
3
|
const configuredProvider = router.configuredIdentity();
|
|
4
4
|
const provider = providerSummary(configuredProvider);
|
|
5
5
|
const providers = router.providerIdentities(route).map(providerSummary);
|
|
@@ -41,6 +41,7 @@ export const binarySessionStatus = ({ target, route, router, runtimeAvailability
|
|
|
41
41
|
run_id: runId,
|
|
42
42
|
process_lineage: processLineageStatus(runtimeLineageSnapshots),
|
|
43
43
|
},
|
|
44
|
+
analysis_activity: requestActivityStatus(requestActivitySnapshots),
|
|
44
45
|
};
|
|
45
46
|
return target === undefined
|
|
46
47
|
? { open: false, ...common }
|
|
@@ -54,6 +55,35 @@ export const binarySessionStatus = ({ target, route, router, runtimeAvailability
|
|
|
54
55
|
architecture: target.architecture ?? null,
|
|
55
56
|
};
|
|
56
57
|
};
|
|
58
|
+
const requestActivityStatus = (snapshots) => {
|
|
59
|
+
const providers = snapshots
|
|
60
|
+
.slice()
|
|
61
|
+
.sort((left, right) => left.provider.id.localeCompare(right.provider.id))
|
|
62
|
+
.map(({ provider, active, queuedRequests }) => ({
|
|
63
|
+
provider: providerSummary(provider),
|
|
64
|
+
active: active === null
|
|
65
|
+
? null
|
|
66
|
+
: {
|
|
67
|
+
request_id: active.requestId,
|
|
68
|
+
operation: active.operation,
|
|
69
|
+
elapsed_ms: active.elapsedMs,
|
|
70
|
+
timeout_ms: active.timeoutMs,
|
|
71
|
+
caller_state: active.callerState,
|
|
72
|
+
},
|
|
73
|
+
queued_requests: queuedRequests,
|
|
74
|
+
}));
|
|
75
|
+
const active = snapshots.flatMap((snapshot) => snapshot.active === null ? [] : [snapshot.active]);
|
|
76
|
+
return {
|
|
77
|
+
status: snapshots.length === 0
|
|
78
|
+
? "not_observed"
|
|
79
|
+
: active.some(({ callerState }) => callerState === "timed_out")
|
|
80
|
+
? "timed_out_busy"
|
|
81
|
+
: active.length > 0
|
|
82
|
+
? "busy"
|
|
83
|
+
: "idle",
|
|
84
|
+
providers,
|
|
85
|
+
};
|
|
86
|
+
};
|
|
57
87
|
const providerSummary = ({ id, name, version, }) => ({ id, name, version });
|
|
58
88
|
const processLineageStatus = (snapshots) => snapshots.length === 0
|
|
59
89
|
? { status: "not_observed" }
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createEvidence, } from "../domain/evidence.js";
|
|
2
2
|
import { jsonValueSchema } from "../domain/jsonValue.js";
|
|
3
|
+
import { commitBrowserScenarioNormalization } from "../domain/browserScenarioNormalization.js";
|
|
3
4
|
/** Create Evidence v2 for a policy-scoped external browser observation. */
|
|
4
5
|
export const createBrowserEvidence = (operation, input, result, provider) => createEvidence(undefined, provider, {
|
|
5
6
|
predicateType: browserPredicate(operation),
|
|
@@ -20,6 +21,18 @@ export const createBrowserEvidence = (operation, input, result, provider) => cre
|
|
|
20
21
|
});
|
|
21
22
|
const browserParameters = (input) => {
|
|
22
23
|
if (!("cdp_endpoint" in input)) {
|
|
24
|
+
if ("before_scenario" in input &&
|
|
25
|
+
input.before_scenario !== undefined &&
|
|
26
|
+
input.after_scenario !== undefined)
|
|
27
|
+
return {
|
|
28
|
+
comparison_kind: "browser_scenario",
|
|
29
|
+
before_browser: input.before_scenario.browser,
|
|
30
|
+
after_browser: input.after_scenario.browser,
|
|
31
|
+
before_start_origin: input.before_scenario.scenario.start_origin,
|
|
32
|
+
after_start_origin: input.after_scenario.scenario.start_origin,
|
|
33
|
+
max_changes: input.max_changes,
|
|
34
|
+
normalization_sha256: commitBrowserScenarioNormalization(input.normalization).sha256,
|
|
35
|
+
};
|
|
23
36
|
if ("max_changes" in input)
|
|
24
37
|
return {
|
|
25
38
|
before_target_id: input.before.inspection.target.target_id,
|
|
@@ -74,6 +87,7 @@ const browserParameters = (input) => {
|
|
|
74
87
|
include_websocket_shapes: input.include_websocket_shapes,
|
|
75
88
|
include_script_sources: input.include_script_sources,
|
|
76
89
|
include_storage_keys: input.include_storage_keys,
|
|
90
|
+
include_storage_fingerprints: input.include_storage_fingerprints,
|
|
77
91
|
limits: input.limits,
|
|
78
92
|
...(input.include_script_sources && "source_capture_approved" in input
|
|
79
93
|
? {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|