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.
Files changed (209) hide show
  1. package/README.md +80 -18
  2. package/bridge/ghidra/ReaGhidraBridge.java +48 -1
  3. package/bridge/hopper_bridge.py +259 -24
  4. package/dist/application/AnalysisClientCleanup.js +14 -0
  5. package/dist/application/ApplicationWorkflowEvidenceResolver.js +31 -13
  6. package/dist/application/ArtifactExtraction.js +5 -1
  7. package/dist/application/ArtifactGraphConstruction.js +3 -15
  8. package/dist/application/ArtifactInventory/classify.js +5 -1
  9. package/dist/application/ArtifactInventory/reader.js +2 -0
  10. package/dist/application/BinarySession.js +15 -4
  11. package/dist/application/BinarySessionStatus.js +32 -1
  12. package/dist/application/BrowserEvidence.js +14 -0
  13. package/dist/application/BrowserScenarioCapturePort.js +1 -0
  14. package/dist/application/BrowserScenarioCaptureService.js +53 -0
  15. package/dist/application/BrowserScenarioEvidence.js +40 -0
  16. package/dist/application/CallPathTracing.js +160 -0
  17. package/dist/application/CapabilityInventory.js +79 -15
  18. package/dist/application/CompositeProvider.js +10 -1
  19. package/dist/application/DirectAnalysis.js +7 -2
  20. package/dist/application/EnhancedLiteralTracing.js +118 -0
  21. package/dist/application/EnhancedTools.js +35 -121
  22. package/dist/application/JavaScriptApplicationWorkflowEvidence.js +13 -0
  23. package/dist/application/JavaScriptApplicationWorkflowService.js +27 -1
  24. package/dist/application/JavaScriptRuntimeObservationEvidence.js +34 -0
  25. package/dist/application/JavaScriptRuntimeObservationPort.js +1 -0
  26. package/dist/application/JavaScriptRuntimeObservationService.js +46 -0
  27. package/dist/application/JavaScriptSemanticGraphAsyncProjection.js +147 -0
  28. package/dist/application/JavaScriptSemanticGraphBuilder.js +34 -6
  29. package/dist/application/JavaScriptSemanticGraphChildProcessProjection.js +156 -0
  30. package/dist/application/JavaScriptSemanticGraphConstruction.js +2 -2
  31. package/dist/application/JavaScriptSemanticGraphDataProjection.js +203 -0
  32. package/dist/application/JavaScriptSemanticGraphFingerprintProjection.js +25 -0
  33. package/dist/application/JavaScriptSemanticGraphFlowProjection.js +129 -8
  34. package/dist/application/JavaScriptSemanticGraphObjectProjection.js +81 -0
  35. package/dist/application/JavaScriptSemanticGraphProjection.js +14 -7
  36. package/dist/application/JavaScriptSemanticGraphResourceProjection.js +59 -0
  37. package/dist/application/JavaScriptSemanticGraphValueProjection.js +77 -0
  38. package/dist/application/ProcessCaptureEnvironment.js +26 -0
  39. package/dist/application/ProcessCaptureLifecycle.js +1 -0
  40. package/dist/application/ProcessCheckpoints.js +5 -0
  41. package/dist/application/ProcessHarness.js +45 -34
  42. package/dist/application/ProcessNormalization.js +6 -0
  43. package/dist/application/ProcessReactiveCoordinator.js +10 -3
  44. package/dist/application/ProcessReactiveEffects.js +15 -1
  45. package/dist/application/ProcessReactiveHarness.js +100 -0
  46. package/dist/application/ProcessReactiveObservations.js +101 -16
  47. package/dist/application/ProjectPermissionStore.js +1 -0
  48. package/dist/application/ReconstructionObligationAuthority.js +72 -0
  49. package/dist/application/ReconstructionObligationCandidates.js +265 -0
  50. package/dist/application/ReconstructionObligationLedgerDiagnostics.js +47 -0
  51. package/dist/application/ReconstructionObligationLedgerEvaluation.js +263 -0
  52. package/dist/application/ReconstructionObligationLedgerProjection.js +86 -0
  53. package/dist/application/ReconstructionObligationLedgerService.js +72 -0
  54. package/dist/application/ReconstructionObligationOriginalCases.js +33 -0
  55. package/dist/application/ReconstructionObligationPolicies.js +132 -0
  56. package/dist/application/ReconstructionReadinessService.js +48 -0
  57. package/dist/artifacts/ArtifactProvider.js +87 -22
  58. package/dist/browser/BrowserScenarioSecrets.js +53 -0
  59. package/dist/browser/BrowserScenarioSessionPort.js +1 -0
  60. package/dist/browser/CdpBrowserProvider.js +2 -2
  61. package/dist/browser/CdpCaptureDocuments.js +57 -0
  62. package/dist/browser/CdpCaptureStorage.js +71 -29
  63. package/dist/browser/CdpCaptureStorageFingerprints.js +328 -0
  64. package/dist/browser/CdpEndpoint.js +8 -6
  65. package/dist/browser/CdpPageCapture.js +2 -0
  66. package/dist/browser/JavaScriptRuntimeScope.js +38 -0
  67. package/dist/browser/PlaywrightBrowserScenarioProvider.js +253 -0
  68. package/dist/browser/PlaywrightExecutionBoundary.js +42 -0
  69. package/dist/browser/PlaywrightScenarioActions.js +62 -0
  70. package/dist/browser/PlaywrightScenarioArtifacts.js +238 -0
  71. package/dist/browser/PlaywrightScenarioBrowser.js +144 -0
  72. package/dist/browser/PlaywrightScenarioEvents.js +301 -0
  73. package/dist/browser/PlaywrightScenarioSession.js +300 -0
  74. package/dist/browser/V8InspectorCaptureProjection.js +110 -0
  75. package/dist/browser/V8InspectorEndpoint.js +72 -0
  76. package/dist/browser/V8InspectorProvider.js +267 -0
  77. package/dist/catalogIdentity.js +8 -0
  78. package/dist/cli/artifactCommands.js +39 -0
  79. package/dist/cli/coreAnalysisCommands.js +37 -0
  80. package/dist/cli.js +4 -0
  81. package/dist/cliApplicationCommands.js +30 -2
  82. package/dist/cliBrowserAdvancedCommands.js +17 -5
  83. package/dist/cliBrowserCommands.js +1 -0
  84. package/dist/cliBrowserScenarioCommands.js +43 -0
  85. package/dist/cliCommandNames.js +8 -0
  86. package/dist/cliJavaScriptRuntimeCommands.js +164 -0
  87. package/dist/cliObservationOptions.js +4 -0
  88. package/dist/cliPolicyCommands.js +1 -0
  89. package/dist/config/environment.js +12 -0
  90. package/dist/config/parseConfig.js +88 -17
  91. package/dist/contracts/analysisSearchContract.js +14 -0
  92. package/dist/contracts/applicationToolContracts.js +86 -2
  93. package/dist/contracts/applicationWorkflowInputContracts.js +18 -0
  94. package/dist/contracts/artifactToolContracts.js +22 -0
  95. package/dist/contracts/browserProviderToolContracts.js +7 -0
  96. package/dist/contracts/browserScenarioToolContracts.js +81 -0
  97. package/dist/contracts/browserToolContracts.js +88 -98
  98. package/dist/contracts/enhancedInputs.js +25 -6
  99. package/dist/contracts/functionInstructionContract.js +8 -0
  100. package/dist/contracts/hopperMemoryContracts.js +22 -0
  101. package/dist/contracts/javascriptApplicationWorkflowExamples.js +42 -0
  102. package/dist/contracts/javascriptRuntimeObservationToolContracts.js +58 -0
  103. package/dist/contracts/reconstructionReadinessExample.js +436 -0
  104. package/dist/contracts/toolContractExamples.js +5 -0
  105. package/dist/contracts/toolContractTypes.js +1 -0
  106. package/dist/contracts/toolContracts.js +13 -21
  107. package/dist/contracts/toolEffects.js +21 -0
  108. package/dist/contracts/toolOutputSchemaGroups.js +70 -20
  109. package/dist/contracts/toolOutputSchemaPrimitives.js +40 -0
  110. package/dist/domain/analysisErrorProjection.js +8 -0
  111. package/dist/domain/analysisSnapshot.js +2 -0
  112. package/dist/domain/artifactGraph.js +2 -0
  113. package/dist/domain/artifactInspection.js +347 -0
  114. package/dist/domain/binaryTarget.js +3 -6
  115. package/dist/domain/browserCaptureComparison.js +82 -0
  116. package/dist/domain/browserObservation.js +8 -1
  117. package/dist/domain/browserObservationErrors.js +1 -0
  118. package/dist/domain/browserObservationSchemas.js +26 -0
  119. package/dist/domain/browserScenario.js +165 -0
  120. package/dist/domain/browserScenarioCapture.js +57 -0
  121. package/dist/domain/browserScenarioCaptureValues.js +204 -0
  122. package/dist/domain/browserScenarioDiff.js +324 -0
  123. package/dist/domain/browserScenarioDiffValues.js +157 -0
  124. package/dist/domain/browserScenarioNormalization.js +45 -0
  125. package/dist/domain/browserScenarioValues.js +372 -0
  126. package/dist/domain/evidence.js +2 -0
  127. package/dist/domain/hopperValues.js +10 -0
  128. package/dist/domain/javascriptRuntimeObservation.js +170 -0
  129. package/dist/domain/javascriptRuntimeReconciliationParsing.js +120 -2
  130. package/dist/domain/javascriptRuntimeReconciliationResult.js +3 -3
  131. package/dist/domain/javascriptRuntimeReconciliationRuntime.js +2 -2
  132. package/dist/domain/javascriptRuntimeReconciliationSchemas.js +1 -1
  133. package/dist/domain/javascriptSemanticAnalysis.js +5 -4
  134. package/dist/domain/javascriptSemanticAsyncEffects.js +253 -0
  135. package/dist/domain/javascriptSemanticChildProcesses.js +237 -0
  136. package/dist/domain/javascriptSemanticDataEffectHelpers.js +125 -0
  137. package/dist/domain/javascriptSemanticDataEffects.js +284 -0
  138. package/dist/domain/javascriptSemanticDerivedAnalysis.js +41 -0
  139. package/dist/domain/javascriptSemanticFingerprints.js +174 -0
  140. package/dist/domain/javascriptSemanticIr.js +21 -1
  141. package/dist/domain/javascriptSemanticObjects.js +115 -0
  142. package/dist/domain/javascriptSemanticPromises.js +290 -0
  143. package/dist/domain/javascriptSemanticResources.js +136 -0
  144. package/dist/domain/javascriptStaticAnalysis.js +4 -3
  145. package/dist/domain/javascriptStaticAnalysisBundler.js +38 -0
  146. package/dist/domain/javascriptStaticAnalysisCalls.js +23 -1
  147. package/dist/domain/javascriptStaticAnalysisHelpers.js +8 -0
  148. package/dist/domain/processCapture.js +71 -1
  149. package/dist/domain/processCaptureReactiveValidation.js +175 -0
  150. package/dist/domain/processCaptureValidation.js +2 -0
  151. package/dist/domain/processObservation.js +15 -6
  152. package/dist/domain/processReactiveRuntime.js +21 -10
  153. package/dist/domain/processReactiveScenario.js +1 -0
  154. package/dist/domain/processReactiveTransition.js +17 -10
  155. package/dist/domain/processScenario.js +21 -0
  156. package/dist/domain/providerCleanupError.js +11 -0
  157. package/dist/domain/reconstructionObligationLedgerSchemas.js +278 -0
  158. package/dist/domain/reconstructionReadiness.js +121 -0
  159. package/dist/domain/reconstructionReadinessFindingHelpers.js +17 -0
  160. package/dist/domain/reconstructionReadinessIntegrityFindings.js +118 -0
  161. package/dist/domain/reconstructionReadinessJourneyFindings.js +193 -0
  162. package/dist/domain/reconstructionReadinessSchemas.js +240 -0
  163. package/dist/domain/sourceToBundleComparison.js +335 -0
  164. package/dist/domain/sourceToBundleComparisonSchemas.js +133 -0
  165. package/dist/domain/sourceToBundleSignals.js +202 -0
  166. package/dist/domain/webCaptureDiff.js +141 -27
  167. package/dist/domain/webScreenshot.js +1 -1
  168. package/dist/domain/zipPackageFormat.js +20 -0
  169. package/dist/generatedPackageMetadata.js +1 -1
  170. package/dist/ghidra/GhidraDefaults.js +1 -1
  171. package/dist/ghidra/GhidraFunctionValues.js +13 -1
  172. package/dist/ghidra/GhidraProviderCapabilities.js +7 -0
  173. package/dist/hopper/HopperCleanup.js +158 -0
  174. package/dist/hopper/HopperClient.js +69 -99
  175. package/dist/hopper/HopperProvider.js +20 -1
  176. package/dist/hopper/HopperRequestQueue.js +187 -0
  177. package/dist/hopper/HopperResponseStream.js +46 -0
  178. package/dist/hopper/protocol.js +1 -0
  179. package/dist/main/transport.js +24 -24
  180. package/dist/main.js +6 -0
  181. package/dist/mcpDoctor.js +25 -2
  182. package/dist/native/NativeMacOSProvider.js +1 -0
  183. package/dist/process/ProcessCommandIdentity.js +19 -0
  184. package/dist/process/ProcessOwnership.js +11 -8
  185. package/dist/server/DynamicToolAvailability.js +70 -0
  186. package/dist/server/createServer.js +46 -1
  187. package/dist/server/lifecycleProgress.js +10 -0
  188. package/dist/server/permissionFailure.js +12 -0
  189. package/dist/server/registerApplicationTools/compareSourceToBundle.js +35 -0
  190. package/dist/server/registerApplicationTools/helpers.js +11 -7
  191. package/dist/server/registerApplicationTools/obligationLedger.js +27 -0
  192. package/dist/server/registerApplicationTools/readiness.js +55 -0
  193. package/dist/server/registerApplicationTools.js +6 -0
  194. package/dist/server/registerArtifactTools.js +9 -1
  195. package/dist/server/registerBrowserScenarioTool.js +25 -0
  196. package/dist/server/registerBrowserTools.js +2 -2
  197. package/dist/server/registerCloseLifecycleTool.js +57 -0
  198. package/dist/server/registerEnhancedTools.js +16 -5
  199. package/dist/server/registerEvidenceResources.js +4 -0
  200. package/dist/server/registerEvidenceTools.js +9 -2
  201. package/dist/server/registerJavaScriptRuntimeObservationTools.js +37 -0
  202. package/dist/server/registerReconstructionObligationLedgerResource.js +39 -0
  203. package/dist/server/registerReconstructionReadinessResource.js +39 -0
  204. package/dist/server/registerSessionStatusTool.js +11 -8
  205. package/dist/server/registerSessionTools.js +11 -57
  206. package/dist/server/sessionAvailabilityPolicy.js +10 -8
  207. package/package.json +12 -6
  208. package/skills/reverse-engineer-anything/SKILL.md +4 -3
  209. 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 (!["call-flow", "closure", "data-flow", "object-flow"].includes(family))
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: makeEnvironment({ ...options, replay, shimReplay }),
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 { entries: eventJournal, recordEvent } = createProcessCaptureJournal();
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 ["terminal_raw", "interaction", "filesystem"].includes(trigger.source)
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
+ };