rea-agents 2.3.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/ArtifactExtraction.js +5 -1
- package/dist/application/ArtifactGraphConstruction.js +3 -15
- package/dist/application/ArtifactInventory/classify.js +5 -1
- package/dist/application/ArtifactInventory/reader.js +2 -0
- package/dist/application/BinarySession.js +15 -4
- package/dist/application/BinarySessionStatus.js +32 -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 +79 -15
- 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/ProcessCaptureEnvironment.js +26 -0
- package/dist/application/ProcessCaptureLifecycle.js +1 -0
- package/dist/application/ProcessCheckpoints.js +5 -0
- package/dist/application/ProcessHarness.js +45 -34
- package/dist/application/ProcessNormalization.js +6 -0
- package/dist/application/ProcessReactiveCoordinator.js +10 -3
- package/dist/application/ProcessReactiveEffects.js +15 -1
- package/dist/application/ProcessReactiveHarness.js +100 -0
- package/dist/application/ProcessReactiveObservations.js +101 -16
- 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 +40 -0
- package/dist/domain/analysisErrorProjection.js +8 -0
- package/dist/domain/analysisSnapshot.js +2 -0
- package/dist/domain/artifactGraph.js +2 -0
- package/dist/domain/artifactInspection.js +347 -0
- package/dist/domain/binaryTarget.js +3 -6
- 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/evidence.js +2 -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 +71 -1
- package/dist/domain/processCaptureReactiveValidation.js +175 -0
- package/dist/domain/processCaptureValidation.js +2 -0
- package/dist/domain/processObservation.js +15 -6
- package/dist/domain/processReactiveRuntime.js +21 -10
- package/dist/domain/processReactiveScenario.js +1 -0
- package/dist/domain/processReactiveTransition.js +17 -10
- package/dist/domain/processScenario.js +21 -0
- 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/domain/zipPackageFormat.js +20 -0
- 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/native/NativeMacOSProvider.js +1 -0
- 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 +4 -3
- package/skills/reverse-engineer-anything/references/native-and-artifacts.md +2 -2
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { createJavaScriptSemanticGraphUnknown } from "../domain/javascriptSemanticGraph.js";
|
|
2
|
+
import { addSemanticGraphNode, addSemanticGraphRelation, addSemanticGraphUnknown, constructSemanticGraphNode, } from "./JavaScriptSemanticGraphConstruction.js";
|
|
3
|
+
import { unknownSemanticEvidence } from "./JavaScriptSemanticGraphEvidence.js";
|
|
4
|
+
import { semanticPropertySlot } from "./JavaScriptSemanticGraphValueProjection.js";
|
|
5
|
+
/** Project static object reads, writes, spreads, and destructuring. */
|
|
6
|
+
export const projectSemanticObjects = (context) => {
|
|
7
|
+
for (const operation of context.ir.objectOperations) {
|
|
8
|
+
const occurrence = addObjectOccurrence(context, operation);
|
|
9
|
+
const slot = operation.objectBindingId === null || operation.propertyName === null
|
|
10
|
+
? null
|
|
11
|
+
: semanticPropertySlot(context, operation.objectBindingId, operation.propertyName);
|
|
12
|
+
if (operation.kind === "read")
|
|
13
|
+
addSemanticGraphRelation(context.state, {
|
|
14
|
+
source: slot,
|
|
15
|
+
target: occurrence,
|
|
16
|
+
relation: "reads-property",
|
|
17
|
+
resolution: operation.resolution === "complete" ? "resolved" : "candidate",
|
|
18
|
+
});
|
|
19
|
+
else if (operation.kind === "write")
|
|
20
|
+
addSemanticGraphRelation(context.state, {
|
|
21
|
+
source: occurrence,
|
|
22
|
+
target: slot,
|
|
23
|
+
relation: "writes-property",
|
|
24
|
+
resolution: operation.resolution === "complete" ? "resolved" : "candidate",
|
|
25
|
+
});
|
|
26
|
+
else if (operation.kind === "destructure")
|
|
27
|
+
addSemanticGraphRelation(context.state, {
|
|
28
|
+
source: slot,
|
|
29
|
+
target: operation.targetBindingId === null
|
|
30
|
+
? undefined
|
|
31
|
+
: context.bindingNodes.get(operation.targetBindingId),
|
|
32
|
+
relation: "destructures",
|
|
33
|
+
resolution: operation.resolution === "complete" ? "resolved" : "candidate",
|
|
34
|
+
});
|
|
35
|
+
else
|
|
36
|
+
addSemanticGraphRelation(context.state, {
|
|
37
|
+
source: operation.objectBindingId === null
|
|
38
|
+
? undefined
|
|
39
|
+
: context.bindingNodes.get(operation.objectBindingId),
|
|
40
|
+
target: occurrence,
|
|
41
|
+
relation: "spreads",
|
|
42
|
+
resolution: operation.resolution === "complete" ? "resolved" : "candidate",
|
|
43
|
+
});
|
|
44
|
+
if (operation.resolution !== "complete")
|
|
45
|
+
addObjectUnknown(context, operation, slot ?? occurrence);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
const addObjectOccurrence = (context, operation) => addSemanticGraphNode(context.state, constructSemanticGraphNode(context.file, {
|
|
49
|
+
kind: "expression",
|
|
50
|
+
roleKey: operation.objectOperationId,
|
|
51
|
+
location: operation.location,
|
|
52
|
+
label: operation.propertyName === null
|
|
53
|
+
? operation.kind
|
|
54
|
+
: `${operation.kind}:${operation.propertyName}`,
|
|
55
|
+
functionNodeId: operation.ownerCallableId === null
|
|
56
|
+
? null
|
|
57
|
+
: (context.callableNodes.get(operation.ownerCallableId)?.node_id ??
|
|
58
|
+
null),
|
|
59
|
+
properties: {
|
|
60
|
+
operation_kind: operation.kind,
|
|
61
|
+
property_name: operation.propertyName,
|
|
62
|
+
},
|
|
63
|
+
}, context.state));
|
|
64
|
+
const addObjectUnknown = (context, operation, node) => {
|
|
65
|
+
const relation = operation.kind === "read"
|
|
66
|
+
? "reads-property"
|
|
67
|
+
: operation.kind === "write"
|
|
68
|
+
? "writes-property"
|
|
69
|
+
: operation.kind === "spread"
|
|
70
|
+
? "spreads"
|
|
71
|
+
: "destructures";
|
|
72
|
+
addSemanticGraphUnknown(context.state, createJavaScriptSemanticGraphUnknown({
|
|
73
|
+
node_id: node?.node_id ?? null,
|
|
74
|
+
family: "object-flow",
|
|
75
|
+
relation_kinds: [relation],
|
|
76
|
+
reason: "ambiguous-target",
|
|
77
|
+
detail: `Static ${operation.kind} object identity is partial.`,
|
|
78
|
+
candidate_node_ids: [],
|
|
79
|
+
evidence: unknownSemanticEvidence(context.file, operation.location),
|
|
80
|
+
}));
|
|
81
|
+
};
|
|
@@ -15,18 +15,25 @@ export const semanticNodesWithinRange = (nodes, range) => nodes.filter((node) =>
|
|
|
15
15
|
contains(range, node.identity.source_range));
|
|
16
16
|
/** Report extractor support without treating missing families as absence. */
|
|
17
17
|
export const semanticFamilyStatus = (family, analysis, truncated) => {
|
|
18
|
-
if (![
|
|
18
|
+
if (![
|
|
19
|
+
"call-flow",
|
|
20
|
+
"boundary",
|
|
21
|
+
"child-process",
|
|
22
|
+
"closure",
|
|
23
|
+
"configuration",
|
|
24
|
+
"data-flow",
|
|
25
|
+
"event",
|
|
26
|
+
"object-flow",
|
|
27
|
+
"promise-ownership",
|
|
28
|
+
"request",
|
|
29
|
+
"resource-lifecycle",
|
|
30
|
+
"timer",
|
|
31
|
+
].includes(family))
|
|
19
32
|
return "unsupported";
|
|
20
33
|
if (truncated)
|
|
21
34
|
return "unknown";
|
|
22
35
|
return analysis.truncated_scopes === 0 ? "partial" : "unknown";
|
|
23
36
|
};
|
|
24
|
-
/** Compare two exact semantic source ranges. */
|
|
25
|
-
export const semanticRangesEqual = (left, right) => left !== null &&
|
|
26
|
-
left.start.line === right.start.line &&
|
|
27
|
-
left.start.column === right.start.column &&
|
|
28
|
-
left.end.line === right.end.line &&
|
|
29
|
-
left.end.column === right.end.column;
|
|
30
37
|
const contains = (outer, inner) => pointCompare(outer.start, inner.start) <= 0 &&
|
|
31
38
|
pointCompare(outer.end, inner.end) >= 0;
|
|
32
39
|
const pointCompare = (left, right) => left.line - right.line || left.column - right.column;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { createJavaScriptSemanticGraphUnknown } from "../domain/javascriptSemanticGraph.js";
|
|
2
|
+
import { addSemanticGraphNode, addSemanticGraphRelation, addSemanticGraphUnknown, constructSemanticGraphNode, } from "./JavaScriptSemanticGraphConstruction.js";
|
|
3
|
+
import { unknownSemanticEvidence } from "./JavaScriptSemanticGraphEvidence.js";
|
|
4
|
+
import { semanticCallSiteAt, } from "./JavaScriptSemanticGraphFlowProjection.js";
|
|
5
|
+
/** Project built-in resource acquisition and exact local release handles. */
|
|
6
|
+
export const projectSemanticResources = (context) => {
|
|
7
|
+
const resourceNodes = new Map(context.ir.resourceOperations.flatMap((operation) => {
|
|
8
|
+
if (operation.kind !== "acquire")
|
|
9
|
+
return [];
|
|
10
|
+
const node = addResourceNode(context, operation);
|
|
11
|
+
return node === null ? [] : [[operation.resourceId, node]];
|
|
12
|
+
}));
|
|
13
|
+
for (const operation of context.ir.resourceOperations)
|
|
14
|
+
if (operation.kind === "acquire")
|
|
15
|
+
addSemanticGraphRelation(context.state, {
|
|
16
|
+
source: semanticCallSiteAt(context, operation.location),
|
|
17
|
+
target: resourceNodes.get(operation.resourceId),
|
|
18
|
+
relation: "acquires",
|
|
19
|
+
resolution: "resolved",
|
|
20
|
+
});
|
|
21
|
+
else
|
|
22
|
+
projectRelease(context, operation, resourceNodes);
|
|
23
|
+
};
|
|
24
|
+
const addResourceNode = (context, operation) => addSemanticGraphNode(context.state, constructSemanticGraphNode(context.file, {
|
|
25
|
+
kind: "resource",
|
|
26
|
+
roleKey: operation.resourceId,
|
|
27
|
+
location: operation.location,
|
|
28
|
+
label: operation.method,
|
|
29
|
+
functionNodeId: operation.ownerCallableId === null
|
|
30
|
+
? null
|
|
31
|
+
: (context.callableNodes.get(operation.ownerCallableId)?.node_id ??
|
|
32
|
+
null),
|
|
33
|
+
properties: { method: operation.method },
|
|
34
|
+
}, context.state));
|
|
35
|
+
const projectRelease = (context, operation, resourceNodes) => {
|
|
36
|
+
const callSite = semanticCallSiteAt(context, operation.location);
|
|
37
|
+
const resources = operation.linkedResourceIds.flatMap((identifier) => {
|
|
38
|
+
const resource = resourceNodes.get(identifier);
|
|
39
|
+
return resource === undefined ? [] : [resource];
|
|
40
|
+
});
|
|
41
|
+
for (const resource of resources)
|
|
42
|
+
addSemanticGraphRelation(context.state, {
|
|
43
|
+
source: callSite,
|
|
44
|
+
target: resource,
|
|
45
|
+
relation: "releases",
|
|
46
|
+
resolution: operation.resolution === "complete" ? "resolved" : "candidate",
|
|
47
|
+
});
|
|
48
|
+
if (operation.resolution === "complete")
|
|
49
|
+
return;
|
|
50
|
+
addSemanticGraphUnknown(context.state, createJavaScriptSemanticGraphUnknown({
|
|
51
|
+
node_id: callSite?.node_id ?? null,
|
|
52
|
+
family: "resource-lifecycle",
|
|
53
|
+
relation_kinds: ["releases"],
|
|
54
|
+
reason: "ambiguous-target",
|
|
55
|
+
detail: `Static ${operation.method} resource ownership is ambiguous.`,
|
|
56
|
+
candidate_node_ids: resources.map(({ node_id: identifier }) => identifier),
|
|
57
|
+
evidence: unknownSemanticEvidence(context.file, operation.location),
|
|
58
|
+
}));
|
|
59
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { addSemanticGraphNode, addSemanticGraphRelation, constructSemanticGraphNode, } from "./JavaScriptSemanticGraphConstruction.js";
|
|
2
|
+
/** Project bounded literal values and object slots for exact query seeds. */
|
|
3
|
+
export const projectSemanticValues = (context) => {
|
|
4
|
+
for (const binding of context.ir.bindings) {
|
|
5
|
+
const bindingNode = context.bindingNodes.get(binding.bindingId);
|
|
6
|
+
if (bindingNode === undefined)
|
|
7
|
+
continue;
|
|
8
|
+
projectValue({
|
|
9
|
+
context,
|
|
10
|
+
binding,
|
|
11
|
+
value: binding.value,
|
|
12
|
+
target: bindingNode,
|
|
13
|
+
role: "binding",
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
const projectValue = (input) => {
|
|
18
|
+
const { context, binding, value, target, role } = input;
|
|
19
|
+
if (target === null)
|
|
20
|
+
return;
|
|
21
|
+
if (value.status === "literal") {
|
|
22
|
+
const literal = addLiteralNode(context, binding, value.value, role);
|
|
23
|
+
addSemanticGraphRelation(context.state, {
|
|
24
|
+
source: literal,
|
|
25
|
+
target,
|
|
26
|
+
relation: "defines",
|
|
27
|
+
resolution: "resolved",
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
else if (value.status === "union")
|
|
31
|
+
for (const primitive of value.values) {
|
|
32
|
+
const literal = addLiteralNode(context, binding, primitive, role);
|
|
33
|
+
addSemanticGraphRelation(context.state, {
|
|
34
|
+
source: literal,
|
|
35
|
+
target,
|
|
36
|
+
relation: "defines",
|
|
37
|
+
resolution: "candidate",
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
else if (value.status === "object")
|
|
41
|
+
for (const property of value.properties) {
|
|
42
|
+
const slot = semanticPropertySlot(context, binding.bindingId, property.name);
|
|
43
|
+
addSemanticGraphRelation(context.state, {
|
|
44
|
+
source: target,
|
|
45
|
+
target: slot,
|
|
46
|
+
relation: "writes-property",
|
|
47
|
+
resolution: value.unknownProperties ? "candidate" : "resolved",
|
|
48
|
+
});
|
|
49
|
+
projectValue({
|
|
50
|
+
context,
|
|
51
|
+
binding,
|
|
52
|
+
value: property.value,
|
|
53
|
+
target: slot,
|
|
54
|
+
role: `property:${property.name}`,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
const addLiteralNode = (context, binding, value, role) => addSemanticGraphNode(context.state, constructSemanticGraphNode(context.file, {
|
|
59
|
+
kind: "literal",
|
|
60
|
+
roleKey: `literal:${binding.bindingId}:${role}:${JSON.stringify(value)}`,
|
|
61
|
+
location: binding.definitions[0]?.location ?? null,
|
|
62
|
+
label: JSON.stringify(value),
|
|
63
|
+
functionNodeId: context.bindingNodes.get(binding.bindingId)?.function_node_id ?? null,
|
|
64
|
+
properties: { value },
|
|
65
|
+
}, context.state));
|
|
66
|
+
/** Create one canonical property slot for a binding/property identity. */
|
|
67
|
+
export const semanticPropertySlot = (context, objectBindingId, name) => addSemanticGraphNode(context.state, constructSemanticGraphNode(context.file, {
|
|
68
|
+
kind: "property-slot",
|
|
69
|
+
roleKey: `property:${objectBindingId}:${name}`,
|
|
70
|
+
location: null,
|
|
71
|
+
label: name,
|
|
72
|
+
functionNodeId: context.bindingNodes.get(objectBindingId)?.function_node_id ?? null,
|
|
73
|
+
properties: {
|
|
74
|
+
name,
|
|
75
|
+
object_binding_id: objectBindingId,
|
|
76
|
+
},
|
|
77
|
+
}, context.state));
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Build the explicitly admitted environment for a captured process. */
|
|
2
|
+
export const makeProcessCaptureEnvironment = (options) => {
|
|
3
|
+
const { scenario, home, replay, shimReplay, runId } = options;
|
|
4
|
+
const environment = {
|
|
5
|
+
...scenario.environment,
|
|
6
|
+
HOME: home,
|
|
7
|
+
TERM: "xterm-256color",
|
|
8
|
+
REA_PROCESS_RUN_ID: runId,
|
|
9
|
+
};
|
|
10
|
+
for (const name of scenario.inherit_environment) {
|
|
11
|
+
const value = process.env[name];
|
|
12
|
+
if (value !== undefined)
|
|
13
|
+
environment[name] = value;
|
|
14
|
+
}
|
|
15
|
+
environment.REA_REPLAY_HTTP_URL = replay.httpUrl;
|
|
16
|
+
environment.REA_REPLAY_WEBSOCKET_URL = replay.websocketUrl;
|
|
17
|
+
environment.REA_SHIM_LEDGER_URL = shimReplay.url;
|
|
18
|
+
// PATH is evidence-producing input. Inheriting the host PATH here would let
|
|
19
|
+
// the authority and reconstruction probe different tools without recording
|
|
20
|
+
// that difference. Scenarios must opt in through environment or
|
|
21
|
+
// inherit_environment; deterministic shims always take precedence.
|
|
22
|
+
environment.PATH = [shimReplay.binPath, environment.PATH ?? ""]
|
|
23
|
+
.filter((part) => part.length > 0)
|
|
24
|
+
.join(":");
|
|
25
|
+
return environment;
|
|
26
|
+
};
|
|
@@ -33,6 +33,7 @@ export const buildCaptureResult = (options) => ({
|
|
|
33
33
|
shim_events: options.shimEvents,
|
|
34
34
|
protocol_events: redactProtocolEvents(options.replay.events, options.scenario),
|
|
35
35
|
replay_transitions: options.replay.transitions,
|
|
36
|
+
reactive_run: options.reactiveRun,
|
|
36
37
|
event_journal: options.eventJournal,
|
|
37
38
|
files_before: options.before.files,
|
|
38
39
|
files_after: options.after.files,
|
|
@@ -79,6 +79,11 @@ export class ProcessCheckpoints {
|
|
|
79
79
|
capture(name) {
|
|
80
80
|
this.#scheduleCapture(name, this.#signal);
|
|
81
81
|
}
|
|
82
|
+
/** Read one committed checkpoint for synchronous journal projection. */
|
|
83
|
+
at(index) {
|
|
84
|
+
const checkpoint = this.#captures[index];
|
|
85
|
+
return checkpoint === undefined ? undefined : structuredClone(checkpoint);
|
|
86
|
+
}
|
|
82
87
|
#scheduleCapture(name, signal) {
|
|
83
88
|
if (this.#captured.has(name))
|
|
84
89
|
return;
|
|
@@ -8,37 +8,14 @@ import { snapshotRoots } from "./FilesystemSnapshot.js";
|
|
|
8
8
|
import { ProcessCheckpoints } from "./ProcessCheckpoints.js";
|
|
9
9
|
import { TerminalRenderer } from "./TerminalRenderer.js";
|
|
10
10
|
import { startCommandShimReplay, } from "./CommandShimReplay.js";
|
|
11
|
-
import { normalizeProcessText } from "./ProcessNormalization.js";
|
|
11
|
+
import { normalizeProcessShimEvent, normalizeProcessText, } from "./ProcessNormalization.js";
|
|
12
12
|
import { selectCapturedProcessGroupIds } from "../process/ProcessOwnership.js";
|
|
13
13
|
import { awaitTerminalExit, buildCaptureResult, captureTerminalFrames, createRunManifest, observeSettlement, prepareProcessCapture, releaseProcessResources, resolveProcessResult, } from "./ProcessCaptureLifecycle.js";
|
|
14
14
|
import { assertNotCancelled } from "./ProcessCaptureAuthority.js";
|
|
15
15
|
import { createProcessCaptureJournal, scheduleScenarioInteractions, } from "./ProcessCaptureJournal.js";
|
|
16
|
+
import { assertSupportedReactiveScenario, projectProcessReactiveRun, startProcessReactiveHarness, } from "./ProcessReactiveHarness.js";
|
|
17
|
+
import { makeProcessCaptureEnvironment } from "./ProcessCaptureEnvironment.js";
|
|
16
18
|
export { probeProcessCaptureCapability } from "./ProcessCaptureCapability.js";
|
|
17
|
-
const makeEnvironment = (options) => {
|
|
18
|
-
const { scenario, home, replay, shimReplay, runId } = options;
|
|
19
|
-
const environment = {
|
|
20
|
-
...scenario.environment,
|
|
21
|
-
HOME: home,
|
|
22
|
-
TERM: "xterm-256color",
|
|
23
|
-
REA_PROCESS_RUN_ID: runId,
|
|
24
|
-
};
|
|
25
|
-
for (const name of scenario.inherit_environment) {
|
|
26
|
-
const value = process.env[name];
|
|
27
|
-
if (value !== undefined)
|
|
28
|
-
environment[name] = value;
|
|
29
|
-
}
|
|
30
|
-
environment.REA_REPLAY_HTTP_URL = replay.httpUrl;
|
|
31
|
-
environment.REA_REPLAY_WEBSOCKET_URL = replay.websocketUrl;
|
|
32
|
-
environment.REA_SHIM_LEDGER_URL = shimReplay.url;
|
|
33
|
-
// PATH is evidence-producing input. Inheriting the host PATH here would let
|
|
34
|
-
// the authority and reconstruction probe different tools without recording
|
|
35
|
-
// that difference. Scenarios must opt in through environment or
|
|
36
|
-
// inherit_environment; deterministic shims always take precedence.
|
|
37
|
-
environment.PATH = [shimReplay.binPath, environment.PATH ?? ""]
|
|
38
|
-
.filter((part) => part.length > 0)
|
|
39
|
-
.join(":");
|
|
40
|
-
return environment;
|
|
41
|
-
};
|
|
42
19
|
const cleanupFailedStartup = async (options) => {
|
|
43
20
|
options.terminal?.kill("SIGKILL");
|
|
44
21
|
const cleanupFailure = await releaseProcessResources({
|
|
@@ -61,6 +38,11 @@ const createTerminalRenderer = (scenario, temporaryRoot, terminalPid, recordEven
|
|
|
61
38
|
normalize: (value) => normalizeProcessText(value, scenario, temporaryRoot, terminalPid()),
|
|
62
39
|
recordEvent,
|
|
63
40
|
});
|
|
41
|
+
const reactiveCapture = (options, protocolEvents) => ({
|
|
42
|
+
...options,
|
|
43
|
+
processSamples: options.samples,
|
|
44
|
+
protocolEvents,
|
|
45
|
+
});
|
|
64
46
|
const startCaptureRuntime = async (options) => {
|
|
65
47
|
const { scenario } = options;
|
|
66
48
|
let replay;
|
|
@@ -68,6 +50,7 @@ const startCaptureRuntime = async (options) => {
|
|
|
68
50
|
let checkpoints;
|
|
69
51
|
let shimReplay;
|
|
70
52
|
let terminal;
|
|
53
|
+
let reactive;
|
|
71
54
|
try {
|
|
72
55
|
const { spawn } = await import("@lydell/node-pty");
|
|
73
56
|
replay = await startLoopbackReplay(scenario, options.recordEvent);
|
|
@@ -82,7 +65,7 @@ const startCaptureRuntime = async (options) => {
|
|
|
82
65
|
shimReplay = await startCommandShimReplay(scenario, options.temporaryRoot, started, options.recordEvent);
|
|
83
66
|
terminal = spawn(scenario.executable, [...scenario.arguments], {
|
|
84
67
|
cwd: scenario.working_directory,
|
|
85
|
-
env:
|
|
68
|
+
env: makeProcessCaptureEnvironment({ ...options, replay, shimReplay }),
|
|
86
69
|
cols: scenario.terminal.columns,
|
|
87
70
|
rows: scenario.terminal.rows,
|
|
88
71
|
name: "xterm-256color",
|
|
@@ -95,6 +78,15 @@ const startCaptureRuntime = async (options) => {
|
|
|
95
78
|
renderer,
|
|
96
79
|
checkpoints,
|
|
97
80
|
});
|
|
81
|
+
reactive = startProcessReactiveHarness({
|
|
82
|
+
capture: reactiveCapture(options, () => replay?.events ?? []),
|
|
83
|
+
scenario,
|
|
84
|
+
terminal: () => terminal,
|
|
85
|
+
renderer,
|
|
86
|
+
checkpoints,
|
|
87
|
+
shimReplay,
|
|
88
|
+
started,
|
|
89
|
+
});
|
|
98
90
|
scheduleScenarioInteractions({
|
|
99
91
|
...options,
|
|
100
92
|
getTerminal: () => terminal,
|
|
@@ -120,9 +112,12 @@ const startCaptureRuntime = async (options) => {
|
|
|
120
112
|
lastOutput: () => lastOutput,
|
|
121
113
|
framesTruncated,
|
|
122
114
|
stopSampler,
|
|
115
|
+
reactive,
|
|
123
116
|
};
|
|
124
117
|
}
|
|
125
118
|
catch (cause) {
|
|
119
|
+
reactive?.unsubscribe();
|
|
120
|
+
await reactive?.coordinator.close();
|
|
126
121
|
return cleanupFailedStartup({
|
|
127
122
|
cause,
|
|
128
123
|
timers: options.timers,
|
|
@@ -136,12 +131,9 @@ const startCaptureRuntime = async (options) => {
|
|
|
136
131
|
});
|
|
137
132
|
}
|
|
138
133
|
};
|
|
139
|
-
const normalizedShimEvents = (runtime, scenario, temporaryRoot) => runtime.shimReplay.events.map((event) => (
|
|
140
|
-
...event,
|
|
141
|
-
arguments: event.arguments.map((argument) => normalizeProcessText(argument, scenario, temporaryRoot, runtime.terminal.pid)),
|
|
142
|
-
working_directory: normalizeProcessText(event.working_directory, scenario, temporaryRoot, runtime.terminal.pid),
|
|
143
|
-
}));
|
|
134
|
+
const normalizedShimEvents = (runtime, scenario, temporaryRoot) => runtime.shimReplay.events.map((event) => normalizeProcessShimEvent(event, scenario, temporaryRoot, runtime.terminal.pid));
|
|
144
135
|
const finishProcessRun = async (options) => {
|
|
136
|
+
options.runtime?.reactive?.unsubscribe();
|
|
145
137
|
await options.stopSampler();
|
|
146
138
|
const cleanupFailure = await releaseProcessResources({
|
|
147
139
|
timers: options.timers,
|
|
@@ -156,10 +148,16 @@ const finishProcessRun = async (options) => {
|
|
|
156
148
|
? []
|
|
157
149
|
: selectCapturedProcessGroupIds(options.runtime.terminal.pid, options.samples),
|
|
158
150
|
});
|
|
151
|
+
if (cleanupFailure !== undefined)
|
|
152
|
+
await options.runtime?.reactive?.coordinator.submit({
|
|
153
|
+
kind: "cleanup_failed",
|
|
154
|
+
});
|
|
155
|
+
await options.runtime?.reactive?.coordinator.close();
|
|
159
156
|
let { capture, executionFailure } = options;
|
|
160
157
|
if (capture !== undefined) {
|
|
161
158
|
capture = {
|
|
162
159
|
...capture,
|
|
160
|
+
reactive_run: projectProcessReactiveRun(options.runtime?.reactive),
|
|
163
161
|
settlement: {
|
|
164
162
|
...capture.settlement,
|
|
165
163
|
cleanup_outcome: capture.settlement.state === "quiesced"
|
|
@@ -179,8 +177,17 @@ const completeCapture = async (options) => {
|
|
|
179
177
|
const { runtime, scenario } = options;
|
|
180
178
|
runtime.checkpoints.trigger("root_exit");
|
|
181
179
|
const { reason } = options.exit;
|
|
182
|
-
if (reason === "cancelled")
|
|
180
|
+
if (reason === "cancelled") {
|
|
181
|
+
if (runtime.reactive !== undefined) {
|
|
182
|
+
await runtime.reactive.coordinator.submit({ kind: "cancelled" });
|
|
183
|
+
runtime.reactive.unsubscribe();
|
|
184
|
+
}
|
|
183
185
|
throw processCaptureCancelled();
|
|
186
|
+
}
|
|
187
|
+
if (runtime.reactive !== undefined) {
|
|
188
|
+
await runtime.reactive.coordinator.submit({ kind: "target_lost" });
|
|
189
|
+
runtime.reactive.unsubscribe();
|
|
190
|
+
}
|
|
184
191
|
const settlement = await observeSettlement(options.runId, selectCapturedProcessGroupIds(runtime.terminal.pid, options.samples), scenario.settle_ms, options.recordEvent);
|
|
185
192
|
runtime.checkpoints.trigger("settled");
|
|
186
193
|
const samplingPartial = (await runtime.stopSampler()).partial;
|
|
@@ -203,6 +210,7 @@ const completeCapture = async (options) => {
|
|
|
203
210
|
exit: { ...options.exit, reason },
|
|
204
211
|
samples: options.samples,
|
|
205
212
|
replay: runtime.replay,
|
|
213
|
+
reactiveRun: projectProcessReactiveRun(runtime.reactive),
|
|
206
214
|
before: options.before,
|
|
207
215
|
after,
|
|
208
216
|
truncated,
|
|
@@ -220,10 +228,12 @@ const completeCapture = async (options) => {
|
|
|
220
228
|
};
|
|
221
229
|
/** Execute one authorized scenario and return bounded observations. */
|
|
222
230
|
const runProcessScenario = async (scenario, policy, signal) => {
|
|
231
|
+
assertSupportedReactiveScenario(scenario);
|
|
223
232
|
const { temporaryRoot, runId, home, before } = await prepareProcessCapture(scenario, policy, signal);
|
|
224
233
|
const frames = [];
|
|
225
234
|
const samples = [];
|
|
226
|
-
const
|
|
235
|
+
const journal = createProcessCaptureJournal();
|
|
236
|
+
const { entries: eventJournal, recordEvent } = journal;
|
|
227
237
|
let runtime;
|
|
228
238
|
const timers = new Set();
|
|
229
239
|
let capture;
|
|
@@ -244,6 +254,7 @@ const runProcessScenario = async (scenario, policy, signal) => {
|
|
|
244
254
|
timers,
|
|
245
255
|
dispatchedEventIndexes,
|
|
246
256
|
recordEvent,
|
|
257
|
+
journal,
|
|
247
258
|
...(signal === undefined ? {} : { signal }),
|
|
248
259
|
});
|
|
249
260
|
stopSampler = runtime.stopSampler;
|
|
@@ -54,6 +54,12 @@ export const normalizeProcessSamples = (samples, scenario, rootPid) => {
|
|
|
54
54
|
command: normalizeCommand(sample.command),
|
|
55
55
|
}));
|
|
56
56
|
};
|
|
57
|
+
/** Normalize one shim observation identically for live matching and capture. */
|
|
58
|
+
export const normalizeProcessShimEvent = (event, scenario, temporaryRoot, rootPid) => ({
|
|
59
|
+
...event,
|
|
60
|
+
arguments: event.arguments.map((argument) => normalizeProcessText(argument, scenario, temporaryRoot, rootPid)),
|
|
61
|
+
working_directory: normalizeProcessText(event.working_directory, scenario, temporaryRoot, rootPid),
|
|
62
|
+
});
|
|
57
63
|
const normalizePidTokens = (value, identifiers) => {
|
|
58
64
|
const tokens = [...new Set(identifiers)]
|
|
59
65
|
.filter((identifier) => Number.isSafeInteger(identifier) && identifier > 0)
|
|
@@ -153,7 +153,7 @@ export class ProcessReactiveCoordinator {
|
|
|
153
153
|
decision = commitProcessReactiveProposal(this.#scenario, decision, effectResults);
|
|
154
154
|
}
|
|
155
155
|
this.#snapshot = decision.snapshot;
|
|
156
|
-
this.#onDecision?.(decision);
|
|
156
|
+
this.#onDecision?.(decision, input);
|
|
157
157
|
if (this.#snapshot.status === "finished")
|
|
158
158
|
this.#clearTimers();
|
|
159
159
|
else if (this.#accepting &&
|
|
@@ -162,16 +162,23 @@ export class ProcessReactiveCoordinator {
|
|
|
162
162
|
this.#snapshot.state_entry_capture_order))
|
|
163
163
|
this.#scheduleStateDeadline();
|
|
164
164
|
for (const result of effectResults)
|
|
165
|
-
if (result.status === "succeeded"
|
|
165
|
+
if (result.status === "succeeded" &&
|
|
166
|
+
!this.#hasObservation(result.observation.event_id))
|
|
166
167
|
this.#enqueueOwned({
|
|
167
168
|
kind: "observation",
|
|
168
169
|
observation: result.observation,
|
|
169
170
|
});
|
|
170
171
|
}
|
|
172
|
+
#hasObservation(eventId) {
|
|
173
|
+
return (this.#snapshot.observations.some((observation) => observation.event_id === eventId) ||
|
|
174
|
+
this.#pendingInputs.some((input) => input.kind === "observation" &&
|
|
175
|
+
input.observation.event_id === eventId));
|
|
176
|
+
}
|
|
171
177
|
#interruptsEffects(input) {
|
|
172
178
|
if (input.kind === "cancelled" ||
|
|
173
179
|
input.kind === "cleanup_failed" ||
|
|
174
|
-
input.kind === "scenario_deadline"
|
|
180
|
+
input.kind === "scenario_deadline" ||
|
|
181
|
+
input.kind === "target_lost")
|
|
175
182
|
return true;
|
|
176
183
|
return (input.kind === "state_deadline" &&
|
|
177
184
|
input.state_id === this.#snapshot.active_state &&
|
|
@@ -7,6 +7,8 @@ const interactionData = (action) => {
|
|
|
7
7
|
: action.data;
|
|
8
8
|
if (action.type === "resize")
|
|
9
9
|
return `${String(action.columns)}x${String(action.rows)}`;
|
|
10
|
+
if (action.type === "close_stdin")
|
|
11
|
+
return "";
|
|
10
12
|
return action.signal;
|
|
11
13
|
};
|
|
12
14
|
const interactionType = (action) => {
|
|
@@ -14,6 +16,8 @@ const interactionType = (action) => {
|
|
|
14
16
|
return "input";
|
|
15
17
|
if (action.type === "resize")
|
|
16
18
|
return "resize";
|
|
19
|
+
if (action.type === "close_stdin")
|
|
20
|
+
return "stdin_close";
|
|
17
21
|
return "signal";
|
|
18
22
|
};
|
|
19
23
|
const performTerminalAction = (terminal, renderer, action, atMs) => {
|
|
@@ -23,6 +27,8 @@ const performTerminalAction = (terminal, renderer, action, atMs) => {
|
|
|
23
27
|
terminal.resize(action.columns, action.rows);
|
|
24
28
|
renderer.resize(action.columns, action.rows, atMs);
|
|
25
29
|
}
|
|
30
|
+
else if (action.type === "close_stdin")
|
|
31
|
+
terminal.closeInput();
|
|
26
32
|
else
|
|
27
33
|
terminal.kill(action.signal);
|
|
28
34
|
};
|
|
@@ -109,7 +115,15 @@ const triggerSources = (trigger) => {
|
|
|
109
115
|
if (trigger.kind === "terminal_text")
|
|
110
116
|
return [];
|
|
111
117
|
if (trigger.kind === "event")
|
|
112
|
-
return [
|
|
118
|
+
return [
|
|
119
|
+
"terminal_raw",
|
|
120
|
+
"interaction",
|
|
121
|
+
"process",
|
|
122
|
+
"filesystem",
|
|
123
|
+
"http",
|
|
124
|
+
"websocket",
|
|
125
|
+
"shim",
|
|
126
|
+
].includes(trigger.source)
|
|
113
127
|
? []
|
|
114
128
|
: [trigger.source];
|
|
115
129
|
return trigger.kind === "repeat"
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { ProcessCaptureError } from "./ProcessCaptureError.js";
|
|
2
|
+
import { ProcessReactiveCoordinator } from "./ProcessReactiveCoordinator.js";
|
|
3
|
+
import { executeProcessReactiveEffects, unsupportedProcessReactiveFeatures, } from "./ProcessReactiveEffects.js";
|
|
4
|
+
import { subscribeProcessReactiveObservations } from "./ProcessReactiveObservations.js";
|
|
5
|
+
import { normalizeProcessSamples, normalizeProcessShimEvent, redactProtocolEvents, } from "./ProcessNormalization.js";
|
|
6
|
+
const reactiveTerminalFor = (terminal) => {
|
|
7
|
+
if (terminal === undefined)
|
|
8
|
+
return undefined;
|
|
9
|
+
return {
|
|
10
|
+
write: (data) => terminal.write(data),
|
|
11
|
+
resize: (columns, rows) => terminal.resize(columns, rows),
|
|
12
|
+
closeInput: () => {
|
|
13
|
+
const end = Reflect.get(terminal, "end");
|
|
14
|
+
if (typeof end !== "function")
|
|
15
|
+
throw new ProcessCaptureError("PTY backend does not expose an stdin-close operation");
|
|
16
|
+
Reflect.apply(end, terminal, []);
|
|
17
|
+
},
|
|
18
|
+
kill: (signal) => terminal.kill(signal),
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
const controlKind = (input) => input.kind === "observation" ? undefined : input.kind;
|
|
22
|
+
/** Reject reactive declarations the current process adapter cannot execute. */
|
|
23
|
+
export const assertSupportedReactiveScenario = (scenario) => {
|
|
24
|
+
if (scenario.reactive === null)
|
|
25
|
+
return;
|
|
26
|
+
const unsupported = unsupportedProcessReactiveFeatures(scenario.reactive);
|
|
27
|
+
if (unsupported.length === 0)
|
|
28
|
+
return;
|
|
29
|
+
throw new ProcessCaptureError(`reactive scenario requests unsupported features: ${unsupported.join(", ")}`);
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Attach the serialized reactive runtime once the target PID is available.
|
|
33
|
+
*
|
|
34
|
+
* Subscription replays journal entries admitted during launcher startup before
|
|
35
|
+
* observing new records, so PID-dependent normalization cannot drop them.
|
|
36
|
+
*/
|
|
37
|
+
export const startProcessReactiveHarness = (options) => {
|
|
38
|
+
if (options.scenario.reactive === null)
|
|
39
|
+
return undefined;
|
|
40
|
+
const controls = [];
|
|
41
|
+
const coordinator = new ProcessReactiveCoordinator({
|
|
42
|
+
scenario: options.scenario.reactive,
|
|
43
|
+
executor: {
|
|
44
|
+
execute: (actions, signal) => executeProcessReactiveEffects({
|
|
45
|
+
terminal: () => reactiveTerminalFor(options.terminal()),
|
|
46
|
+
renderer: options.renderer,
|
|
47
|
+
checkpoints: options.checkpoints,
|
|
48
|
+
interactions: options.capture.interactions,
|
|
49
|
+
journal: options.capture.journal,
|
|
50
|
+
startedAtMs: options.started,
|
|
51
|
+
now: Date.now,
|
|
52
|
+
}, actions, signal),
|
|
53
|
+
},
|
|
54
|
+
onDecision: (decision, input) => {
|
|
55
|
+
const kind = controlKind(input);
|
|
56
|
+
if (kind === undefined || decision.kind !== "finished")
|
|
57
|
+
return;
|
|
58
|
+
controls.push({
|
|
59
|
+
sequence: controls.length,
|
|
60
|
+
kind,
|
|
61
|
+
after_capture_order: decision.snapshot.observations.at(-1)?.capture_order ?? -1,
|
|
62
|
+
});
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
return {
|
|
66
|
+
coordinator,
|
|
67
|
+
controls,
|
|
68
|
+
unsubscribe: subscribeProcessReactiveObservations({
|
|
69
|
+
journal: options.capture.journal,
|
|
70
|
+
frames: options.capture.frames,
|
|
71
|
+
interactions: options.capture.interactions,
|
|
72
|
+
checkpointAt: (index) => options.checkpoints.at(index),
|
|
73
|
+
processSampleAt: (index) => {
|
|
74
|
+
const rootPid = options.terminal()?.pid;
|
|
75
|
+
return rootPid === undefined
|
|
76
|
+
? undefined
|
|
77
|
+
: normalizeProcessSamples(options.capture.processSamples, options.scenario, rootPid)[index];
|
|
78
|
+
},
|
|
79
|
+
protocolEventAt: (index) => redactProtocolEvents(options.capture.protocolEvents().slice(index, index + 1), options.scenario)[0],
|
|
80
|
+
shimEventAt: (index) => {
|
|
81
|
+
const event = options.shimReplay.events[index];
|
|
82
|
+
const rootPid = options.terminal()?.pid;
|
|
83
|
+
return event === undefined || rootPid === undefined
|
|
84
|
+
? undefined
|
|
85
|
+
: normalizeProcessShimEvent(event, options.scenario, options.capture.temporaryRoot, rootPid);
|
|
86
|
+
},
|
|
87
|
+
sink: coordinator,
|
|
88
|
+
}),
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
/** Project internal reducer state into the stable Process Capture shape. */
|
|
92
|
+
export const projectProcessReactiveRun = (harness) => harness === undefined
|
|
93
|
+
? null
|
|
94
|
+
: {
|
|
95
|
+
status: harness.coordinator.snapshot.status,
|
|
96
|
+
outcome: harness.coordinator.snapshot.outcome,
|
|
97
|
+
active_state: harness.coordinator.snapshot.active_state,
|
|
98
|
+
transitions: harness.coordinator.snapshot.transitions,
|
|
99
|
+
controls: harness.controls,
|
|
100
|
+
};
|