rea-agents 2.2.0 → 2.4.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 +57 -10
- package/dist/application/ApplicationWorkflowEvidenceResolver.js +162 -0
- 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 +9 -2
- package/dist/application/BinarySessionStatus.js +40 -1
- package/dist/application/CapabilityInventory.js +26 -3
- package/dist/application/CommandShimReplay.js +6 -3
- package/dist/application/CompositeProvider.js +5 -2
- package/dist/application/DirectAnalysis.js +1 -20
- package/dist/application/DirectAnalysisStatus.js +83 -0
- package/dist/application/Doctor.js +34 -25
- package/dist/application/DoctorDiagnostics.js +1 -4
- package/dist/application/DoctorProjection.js +43 -0
- package/dist/application/DoctorScope.js +102 -0
- package/dist/application/EvidenceReferenceResolver.js +26 -0
- package/dist/application/JavaScriptApplicationEvidence.js +1 -1
- package/dist/application/JavaScriptApplicationEvidenceGraph.js +10 -1
- package/dist/application/JavaScriptApplicationService.js +3 -2
- package/dist/application/JavaScriptApplicationWorkflowEvidence.js +26 -0
- package/dist/application/JavaScriptApplicationWorkflowService.js +49 -1
- package/dist/application/JavaScriptArtifactReconstruction.js +7 -0
- package/dist/application/JavaScriptModuleRelationships.js +25 -1
- package/dist/application/JavaScriptReturnShapeProjection.js +111 -0
- package/dist/application/JavaScriptSemanticGraphBuilder.js +294 -0
- package/dist/application/JavaScriptSemanticGraphConstruction.js +144 -0
- package/dist/application/JavaScriptSemanticGraphEvidence.js +86 -0
- package/dist/application/JavaScriptSemanticGraphFlowProjection.js +72 -0
- package/dist/application/JavaScriptSemanticGraphProjection.js +33 -0
- package/dist/application/JavaScriptSemanticTraceService.js +45 -0
- package/dist/application/LoopbackReplay.js +277 -133
- package/dist/application/LoopbackReplayRecorder.js +101 -0
- package/dist/application/PermissionAuthority.js +7 -3
- package/dist/application/ProcessCaptureEnvironment.js +26 -0
- package/dist/application/ProcessCaptureJournal.js +95 -0
- package/dist/application/ProcessCaptureLifecycle.js +25 -13
- package/dist/application/ProcessCheckpoints.js +35 -4
- package/dist/application/ProcessCli.js +26 -4
- package/dist/application/ProcessHarness.js +64 -83
- package/dist/application/ProcessNormalization.js +9 -2
- package/dist/application/ProcessReactiveCoordinator.js +225 -0
- package/dist/application/ProcessReactiveEffects.js +141 -0
- package/dist/application/ProcessReactiveHarness.js +85 -0
- package/dist/application/ProcessReactiveObservations.js +105 -0
- package/dist/application/ProcessSampling.js +2 -0
- package/dist/application/SessionProviderRouter.js +7 -6
- package/dist/application/Setup.js +11 -10
- package/dist/application/SetupPlan.js +1 -1
- package/dist/application/SetupRegistrationEnvironment.js +12 -0
- package/dist/application/TerminalRenderer.js +3 -1
- package/dist/browser/CdpEndpoint.js +37 -4
- package/dist/catalogIdentity.js +16 -7
- package/dist/cli/coreAnalysisCommands.js +34 -2
- package/dist/cli/javascriptApplicationAnalysis.js +19 -0
- package/dist/cli/setupCommands.js +81 -32
- package/dist/cli/utilityCommands.js +10 -2
- package/dist/cliApplicationCommands.js +79 -16
- package/dist/cliCommandNames.js +3 -0
- package/dist/cliElectronCommands.js +23 -33
- package/dist/cliObservationOptions.js +1 -1
- package/dist/cliOutput.js +56 -0
- package/dist/cliProcessCommands.js +32 -1
- package/dist/cliSetup.js +30 -21
- package/dist/contracts/applicationToolContracts.js +54 -7
- package/dist/contracts/applicationWorkflowInputContracts.js +119 -0
- package/dist/contracts/electronToolContracts.js +53 -32
- package/dist/contracts/javascriptApplicationWorkflowExamples.js +32 -9
- package/dist/contracts/processCaptureExample.js +1 -0
- package/dist/contracts/replayMachineExample.js +37 -0
- package/dist/contracts/toolContractExamples.js +2 -0
- package/dist/contracts/toolContracts.js +5 -1
- package/dist/contracts/toolEffects.js +3 -0
- package/dist/contracts/toolOutputSchemaGroups.js +2 -0
- package/dist/contracts/toolOutputSchemaPrimitives.js +43 -0
- package/dist/doctorRuntime.js +41 -0
- package/dist/domain/analysisErrorPresentation.js +5 -3
- package/dist/domain/analysisErrorProjection.js +7 -1
- package/dist/domain/analysisSnapshot.js +2 -0
- package/dist/domain/artifactGraph.js +2 -0
- package/dist/domain/binaryTarget.js +3 -6
- package/dist/domain/changedBehavior.js +5 -4
- package/dist/domain/completionLedgerGeneration.js +190 -0
- package/dist/domain/errors.js +4 -2
- package/dist/domain/evidence.js +2 -0
- package/dist/domain/evidenceCompletionLedger.js +113 -0
- package/dist/domain/hopperStartupFailure.js +9 -0
- package/dist/domain/javascriptApplicationAnalysis.js +44 -2
- package/dist/domain/javascriptExportShapeComparison.js +145 -0
- package/dist/domain/javascriptExportShapeComparisonIdentity.js +11 -0
- package/dist/domain/javascriptExportShapeComparisonSchemas.js +277 -0
- package/dist/domain/javascriptExportShapeSelection.js +132 -0
- package/dist/domain/javascriptExportShapeVariants.js +272 -0
- package/dist/domain/javascriptRuntimeReconciliationParsing.js +2 -2
- package/dist/domain/javascriptSemanticAnalysis.js +17 -36
- package/dist/domain/javascriptSemanticCallResolution.js +129 -0
- package/dist/domain/javascriptSemanticCalls.js +269 -0
- package/dist/domain/javascriptSemanticGraph.js +356 -0
- package/dist/domain/javascriptSemanticGraphSchemas.js +329 -0
- package/dist/domain/javascriptSemanticGraphSerialization.js +23 -0
- package/dist/domain/javascriptSemanticIr.js +14 -1
- package/dist/domain/javascriptSemanticPrimitives.js +36 -0
- package/dist/domain/javascriptSemanticProjection.js +36 -4
- package/dist/domain/javascriptSemanticProvenance.js +29 -0
- package/dist/domain/javascriptSemanticQuery.js +294 -0
- package/dist/domain/javascriptSemanticQueryAssessment.js +74 -0
- package/dist/domain/javascriptSemanticQueryIdentity.js +86 -0
- package/dist/domain/javascriptSemanticQueryRelations.js +15 -0
- package/dist/domain/javascriptSemanticQuerySchemas.js +188 -0
- package/dist/domain/javascriptSemanticReturns.js +187 -0
- package/dist/domain/javascriptSemanticState.js +32 -0
- package/dist/domain/javascriptSemanticTraceSchemas.js +14 -0
- package/dist/domain/javascriptSemanticValues.js +54 -70
- package/dist/domain/processCapture.js +100 -0
- package/dist/domain/processCaptureReactiveValidation.js +175 -0
- package/dist/domain/processCaptureValidation.js +103 -12
- package/dist/domain/processComparison.js +78 -25
- package/dist/domain/processObservation.js +162 -0
- package/dist/domain/processReactiveCheckpointDataflow.js +32 -0
- package/dist/domain/processReactiveMatching.js +210 -0
- package/dist/domain/processReactiveRuntime.js +120 -0
- package/dist/domain/processReactiveScenario.js +361 -0
- package/dist/domain/processReactiveScenarioPreflight.js +120 -0
- package/dist/domain/processReactiveTransition.js +161 -0
- package/dist/domain/processScenario.js +33 -0
- package/dist/domain/processTraceComparison.js +43 -0
- package/dist/domain/processTraceDimensionProjection.js +50 -0
- package/dist/domain/processTraceEvaluation.js +312 -0
- package/dist/domain/processTraceSpecification.js +373 -0
- package/dist/domain/reconstructionVerification.js +5 -4
- package/dist/domain/replayMachine.js +18 -2
- package/dist/domain/replayMachineRun.js +216 -0
- package/dist/domain/replayMachineRuntime.js +88 -9
- package/dist/domain/staticRuntimeCorrelation.js +4 -2
- package/dist/domain/zipPackageFormat.js +20 -0
- package/dist/evaluation/CodexAgentEval.js +24 -0
- package/dist/generatedPackageMetadata.js +1 -1
- package/dist/ghidra/GhidraClient.js +13 -2
- package/dist/ghidra/GhidraDiagnostics.js +1 -0
- package/dist/ghidra/GhidraLauncher.js +7 -3
- package/dist/ghidra/GhidraProvider.js +8 -1
- package/dist/hopper/BridgeLauncher.js +56 -14
- package/dist/hopper/HopperClient.js +23 -10
- package/dist/hopper/HopperDiagnostics.js +14 -0
- package/dist/hopper/HopperProcessDiagnostic.js +14 -0
- package/dist/hopper/HopperProvider.js +8 -1
- package/dist/hopper/LinuxPrivateDisplayDiagnostic.js +72 -0
- package/dist/hopper/LinuxPrivateDisplayProbe.js +217 -0
- package/dist/main/transport.js +2 -0
- package/dist/mcpDoctor.js +310 -0
- package/dist/native/NativeMacOSProvider.js +1 -0
- package/dist/process/ProcessOwnership.js +200 -53
- package/dist/process/ProviderRunLineage.js +21 -0
- package/dist/server/createServer.js +4 -1
- package/dist/server/javascriptApplicationResult.js +22 -1
- package/dist/server/registerApplicationTools/characterization.js +3 -3
- package/dist/server/registerApplicationTools/compareExportShapes.js +37 -0
- package/dist/server/registerApplicationTools/compareVersions.js +12 -6
- package/dist/server/registerApplicationTools/controlledReplay.js +2 -2
- package/dist/server/registerApplicationTools/coverage.js +3 -3
- package/dist/server/registerApplicationTools/helpers.js +37 -25
- package/dist/server/registerApplicationTools/traceFeature.js +9 -5
- package/dist/server/registerApplicationTools/traceSemantics.js +30 -0
- package/dist/server/registerApplicationTools.js +4 -0
- package/dist/server/registerEvidenceResources.js +5 -1
- package/dist/server/registerProcessComparisonTool.js +17 -7
- package/dist/server/registerReplayMachineTool.js +20 -0
- package/dist/server/registerSessionTools.js +12 -7
- package/dist/server/sessionAvailabilityPolicy.js +2 -1
- package/dist/serverIdentity.js +19 -0
- package/package.json +62 -30
- package/scripts/hopper-demo-x11.py +459 -61
- package/scripts/prepack.mjs +55 -0
- package/scripts/prepare.mjs +28 -0
- package/scripts/rea.mjs +26 -5
- package/skills/reverse-engineer-anything/SKILL.md +4 -3
- package/skills/reverse-engineer-anything/references/javascript-applications.md +10 -0
- package/skills/reverse-engineer-anything/references/native-and-artifacts.md +2 -2
|
@@ -8,79 +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
|
+
import { createProcessCaptureJournal, scheduleScenarioInteractions, } from "./ProcessCaptureJournal.js";
|
|
16
|
+
import { assertSupportedReactiveScenario, projectProcessReactiveRun, startProcessReactiveHarness, } from "./ProcessReactiveHarness.js";
|
|
17
|
+
import { makeProcessCaptureEnvironment } from "./ProcessCaptureEnvironment.js";
|
|
15
18
|
export { probeProcessCaptureCapability } from "./ProcessCaptureCapability.js";
|
|
16
|
-
const makeEnvironment = (options) => {
|
|
17
|
-
const { scenario, home, replay, shimReplay, runId } = options;
|
|
18
|
-
const environment = {
|
|
19
|
-
...scenario.environment,
|
|
20
|
-
HOME: home,
|
|
21
|
-
TERM: "xterm-256color",
|
|
22
|
-
REA_PROCESS_RUN_ID: runId,
|
|
23
|
-
};
|
|
24
|
-
for (const name of scenario.inherit_environment) {
|
|
25
|
-
const value = process.env[name];
|
|
26
|
-
if (value !== undefined)
|
|
27
|
-
environment[name] = value;
|
|
28
|
-
}
|
|
29
|
-
environment.REA_REPLAY_HTTP_URL = replay.httpUrl;
|
|
30
|
-
environment.REA_REPLAY_WEBSOCKET_URL = replay.websocketUrl;
|
|
31
|
-
environment.REA_SHIM_LEDGER_URL = shimReplay.url;
|
|
32
|
-
// PATH is evidence-producing input. Inheriting the host PATH here would let
|
|
33
|
-
// the authority and reconstruction probe different tools without recording
|
|
34
|
-
// that difference. Scenarios must opt in through environment or
|
|
35
|
-
// inherit_environment; deterministic shims always take precedence.
|
|
36
|
-
environment.PATH = [shimReplay.binPath, environment.PATH ?? ""]
|
|
37
|
-
.filter((part) => part.length > 0)
|
|
38
|
-
.join(":");
|
|
39
|
-
return environment;
|
|
40
|
-
};
|
|
41
|
-
const scheduleScenarioEvents = (options) => {
|
|
42
|
-
const { scenario, getTerminal, timers, interactions, renderer, started } = options;
|
|
43
|
-
for (const [eventIndex, event] of scenario.events.entries()) {
|
|
44
|
-
const timer = setTimeout(() => {
|
|
45
|
-
options.dispatchedEventIndexes.add(eventIndex);
|
|
46
|
-
const terminal = getTerminal();
|
|
47
|
-
const dispatchedAt = Math.max(0, Date.now() - started);
|
|
48
|
-
let outcome = "dispatched";
|
|
49
|
-
if (terminal === undefined)
|
|
50
|
-
outcome = "target_exited";
|
|
51
|
-
else {
|
|
52
|
-
try {
|
|
53
|
-
if (event.type === "input")
|
|
54
|
-
terminal.write(event.data);
|
|
55
|
-
else if (event.type === "resize") {
|
|
56
|
-
terminal.resize(event.columns, event.rows);
|
|
57
|
-
renderer.resize(event.columns, event.rows, dispatchedAt);
|
|
58
|
-
}
|
|
59
|
-
else
|
|
60
|
-
terminal.kill(event.signal);
|
|
61
|
-
}
|
|
62
|
-
catch {
|
|
63
|
-
outcome = "failed";
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
interactions.push({
|
|
67
|
-
sequence: interactions.length,
|
|
68
|
-
scheduled_at_ms: event.at_ms,
|
|
69
|
-
dispatched_at_ms: dispatchedAt,
|
|
70
|
-
type: event.type,
|
|
71
|
-
data: event.type === "input"
|
|
72
|
-
? event.sensitive
|
|
73
|
-
? `<redacted-input:${String(Buffer.byteLength(event.data))}-bytes>`
|
|
74
|
-
: normalizeProcessText(event.data, scenario, "<no-temporary-root>", -1)
|
|
75
|
-
: event.type === "resize"
|
|
76
|
-
? `${String(event.columns)}x${String(event.rows)}`
|
|
77
|
-
: event.signal,
|
|
78
|
-
outcome,
|
|
79
|
-
});
|
|
80
|
-
}, event.at_ms);
|
|
81
|
-
timers.add(timer);
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
19
|
const cleanupFailedStartup = async (options) => {
|
|
85
20
|
options.terminal?.kill("SIGKILL");
|
|
86
21
|
const cleanupFailure = await releaseProcessResources({
|
|
@@ -94,13 +29,19 @@ const cleanupFailedStartup = async (options) => {
|
|
|
94
29
|
});
|
|
95
30
|
throw options.cause;
|
|
96
31
|
};
|
|
97
|
-
const createTerminalRenderer = (scenario, temporaryRoot, terminalPid) => new TerminalRenderer({
|
|
32
|
+
const createTerminalRenderer = (scenario, temporaryRoot, terminalPid, recordEvent) => new TerminalRenderer({
|
|
98
33
|
columns: scenario.terminal.columns,
|
|
99
34
|
rows: scenario.terminal.rows,
|
|
100
35
|
scrollback: scenario.terminal.scrollback,
|
|
101
36
|
maxFrames: scenario.limits.frames,
|
|
102
37
|
maxBytes: scenario.limits.output_bytes,
|
|
103
38
|
normalize: (value) => normalizeProcessText(value, scenario, temporaryRoot, terminalPid()),
|
|
39
|
+
recordEvent,
|
|
40
|
+
});
|
|
41
|
+
const reactiveCapture = (options, protocolEvents) => ({
|
|
42
|
+
...options,
|
|
43
|
+
processSamples: options.samples,
|
|
44
|
+
protocolEvents,
|
|
104
45
|
});
|
|
105
46
|
const startCaptureRuntime = async (options) => {
|
|
106
47
|
const { scenario } = options;
|
|
@@ -109,18 +50,22 @@ const startCaptureRuntime = async (options) => {
|
|
|
109
50
|
let checkpoints;
|
|
110
51
|
let shimReplay;
|
|
111
52
|
let terminal;
|
|
53
|
+
let reactive;
|
|
112
54
|
try {
|
|
113
55
|
const { spawn } = await import("@lydell/node-pty");
|
|
114
|
-
replay = await startLoopbackReplay(scenario);
|
|
56
|
+
replay = await startLoopbackReplay(scenario, options.recordEvent);
|
|
115
57
|
const startedAt = new Date();
|
|
116
58
|
const started = Date.now();
|
|
117
59
|
let lastOutput = started;
|
|
118
|
-
renderer = createTerminalRenderer(scenario, options.temporaryRoot, () => terminal?.pid ?? -1);
|
|
119
|
-
checkpoints = new ProcessCheckpoints(scenario, started, options.before,
|
|
120
|
-
|
|
60
|
+
renderer = createTerminalRenderer(scenario, options.temporaryRoot, () => terminal?.pid ?? -1, options.recordEvent);
|
|
61
|
+
checkpoints = new ProcessCheckpoints(scenario, started, options.before, {
|
|
62
|
+
signal: options.signal,
|
|
63
|
+
recordEvent: options.recordEvent,
|
|
64
|
+
});
|
|
65
|
+
shimReplay = await startCommandShimReplay(scenario, options.temporaryRoot, started, options.recordEvent);
|
|
121
66
|
terminal = spawn(scenario.executable, [...scenario.arguments], {
|
|
122
67
|
cwd: scenario.working_directory,
|
|
123
|
-
env:
|
|
68
|
+
env: makeProcessCaptureEnvironment({ ...options, replay, shimReplay }),
|
|
124
69
|
cols: scenario.terminal.columns,
|
|
125
70
|
rows: scenario.terminal.rows,
|
|
126
71
|
name: "xterm-256color",
|
|
@@ -133,7 +78,16 @@ const startCaptureRuntime = async (options) => {
|
|
|
133
78
|
renderer,
|
|
134
79
|
checkpoints,
|
|
135
80
|
});
|
|
136
|
-
|
|
81
|
+
reactive = startProcessReactiveHarness({
|
|
82
|
+
capture: reactiveCapture(options, () => replay?.events ?? []),
|
|
83
|
+
scenario,
|
|
84
|
+
terminal: () => terminal,
|
|
85
|
+
renderer,
|
|
86
|
+
checkpoints,
|
|
87
|
+
shimReplay,
|
|
88
|
+
started,
|
|
89
|
+
});
|
|
90
|
+
scheduleScenarioInteractions({
|
|
137
91
|
...options,
|
|
138
92
|
getTerminal: () => terminal,
|
|
139
93
|
renderer,
|
|
@@ -145,6 +99,7 @@ const startCaptureRuntime = async (options) => {
|
|
|
145
99
|
started,
|
|
146
100
|
limit: scenario.limits.processes,
|
|
147
101
|
samples: options.samples,
|
|
102
|
+
recordEvent: options.recordEvent,
|
|
148
103
|
});
|
|
149
104
|
return {
|
|
150
105
|
replay,
|
|
@@ -157,9 +112,12 @@ const startCaptureRuntime = async (options) => {
|
|
|
157
112
|
lastOutput: () => lastOutput,
|
|
158
113
|
framesTruncated,
|
|
159
114
|
stopSampler,
|
|
115
|
+
reactive,
|
|
160
116
|
};
|
|
161
117
|
}
|
|
162
118
|
catch (cause) {
|
|
119
|
+
reactive?.unsubscribe();
|
|
120
|
+
await reactive?.coordinator.close();
|
|
163
121
|
return cleanupFailedStartup({
|
|
164
122
|
cause,
|
|
165
123
|
timers: options.timers,
|
|
@@ -173,12 +131,9 @@ const startCaptureRuntime = async (options) => {
|
|
|
173
131
|
});
|
|
174
132
|
}
|
|
175
133
|
};
|
|
176
|
-
const normalizedShimEvents = (runtime, scenario, temporaryRoot) => runtime.shimReplay.events.map((event) => (
|
|
177
|
-
...event,
|
|
178
|
-
arguments: event.arguments.map((argument) => normalizeProcessText(argument, scenario, temporaryRoot, runtime.terminal.pid)),
|
|
179
|
-
working_directory: normalizeProcessText(event.working_directory, scenario, temporaryRoot, runtime.terminal.pid),
|
|
180
|
-
}));
|
|
134
|
+
const normalizedShimEvents = (runtime, scenario, temporaryRoot) => runtime.shimReplay.events.map((event) => normalizeProcessShimEvent(event, scenario, temporaryRoot, runtime.terminal.pid));
|
|
181
135
|
const finishProcessRun = async (options) => {
|
|
136
|
+
options.runtime?.reactive?.unsubscribe();
|
|
182
137
|
await options.stopSampler();
|
|
183
138
|
const cleanupFailure = await releaseProcessResources({
|
|
184
139
|
timers: options.timers,
|
|
@@ -193,10 +148,16 @@ const finishProcessRun = async (options) => {
|
|
|
193
148
|
? []
|
|
194
149
|
: selectCapturedProcessGroupIds(options.runtime.terminal.pid, options.samples),
|
|
195
150
|
});
|
|
151
|
+
if (cleanupFailure !== undefined)
|
|
152
|
+
await options.runtime?.reactive?.coordinator.submit({
|
|
153
|
+
kind: "cleanup_failed",
|
|
154
|
+
});
|
|
155
|
+
await options.runtime?.reactive?.coordinator.close();
|
|
196
156
|
let { capture, executionFailure } = options;
|
|
197
157
|
if (capture !== undefined) {
|
|
198
158
|
capture = {
|
|
199
159
|
...capture,
|
|
160
|
+
reactive_run: projectProcessReactiveRun(options.runtime?.reactive),
|
|
200
161
|
settlement: {
|
|
201
162
|
...capture.settlement,
|
|
202
163
|
cleanup_outcome: capture.settlement.state === "quiesced"
|
|
@@ -216,15 +177,25 @@ const completeCapture = async (options) => {
|
|
|
216
177
|
const { runtime, scenario } = options;
|
|
217
178
|
runtime.checkpoints.trigger("root_exit");
|
|
218
179
|
const { reason } = options.exit;
|
|
219
|
-
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
|
+
}
|
|
220
185
|
throw processCaptureCancelled();
|
|
221
|
-
|
|
186
|
+
}
|
|
187
|
+
if (runtime.reactive !== undefined) {
|
|
188
|
+
await runtime.reactive.coordinator.submit({ kind: "target_lost" });
|
|
189
|
+
runtime.reactive.unsubscribe();
|
|
190
|
+
}
|
|
191
|
+
const settlement = await observeSettlement(options.runId, selectCapturedProcessGroupIds(runtime.terminal.pid, options.samples), scenario.settle_ms, options.recordEvent);
|
|
222
192
|
runtime.checkpoints.trigger("settled");
|
|
223
193
|
const samplingPartial = (await runtime.stopSampler()).partial;
|
|
224
194
|
assertNotCancelled(options.signal);
|
|
225
195
|
const after = await snapshotRoots(scenario, options.signal);
|
|
226
196
|
const renderedFrames = await runtime.renderer.frames();
|
|
227
197
|
const checkpoints = await runtime.checkpoints.finish(after);
|
|
198
|
+
await runtime.replay.close();
|
|
228
199
|
const manifest = await createRunManifest(scenario, runtime.startedAt, new Date());
|
|
229
200
|
const truncated = options.initiallyTruncated ||
|
|
230
201
|
after.truncated ||
|
|
@@ -239,6 +210,7 @@ const completeCapture = async (options) => {
|
|
|
239
210
|
exit: { ...options.exit, reason },
|
|
240
211
|
samples: options.samples,
|
|
241
212
|
replay: runtime.replay,
|
|
213
|
+
reactiveRun: projectProcessReactiveRun(runtime.reactive),
|
|
242
214
|
before: options.before,
|
|
243
215
|
after,
|
|
244
216
|
truncated,
|
|
@@ -251,13 +223,17 @@ const completeCapture = async (options) => {
|
|
|
251
223
|
shimEvents: normalizedShimEvents(runtime, scenario, options.temporaryRoot),
|
|
252
224
|
settlement: { ...settlement, cleanup_outcome: "not_required" },
|
|
253
225
|
manifest,
|
|
226
|
+
eventJournal: options.eventJournal,
|
|
254
227
|
});
|
|
255
228
|
};
|
|
256
229
|
/** Execute one authorized scenario and return bounded observations. */
|
|
257
230
|
const runProcessScenario = async (scenario, policy, signal) => {
|
|
231
|
+
assertSupportedReactiveScenario(scenario);
|
|
258
232
|
const { temporaryRoot, runId, home, before } = await prepareProcessCapture(scenario, policy, signal);
|
|
259
233
|
const frames = [];
|
|
260
234
|
const samples = [];
|
|
235
|
+
const journal = createProcessCaptureJournal();
|
|
236
|
+
const { entries: eventJournal, recordEvent } = journal;
|
|
261
237
|
let runtime;
|
|
262
238
|
const timers = new Set();
|
|
263
239
|
let capture;
|
|
@@ -277,6 +253,8 @@ const runProcessScenario = async (scenario, policy, signal) => {
|
|
|
277
253
|
interactions,
|
|
278
254
|
timers,
|
|
279
255
|
dispatchedEventIndexes,
|
|
256
|
+
recordEvent,
|
|
257
|
+
journal,
|
|
280
258
|
...(signal === undefined ? {} : { signal }),
|
|
281
259
|
});
|
|
282
260
|
stopSampler = runtime.stopSampler;
|
|
@@ -289,6 +267,7 @@ const runProcessScenario = async (scenario, policy, signal) => {
|
|
|
289
267
|
timers,
|
|
290
268
|
interactions,
|
|
291
269
|
dispatchedEventIndexes,
|
|
270
|
+
recordEvent,
|
|
292
271
|
});
|
|
293
272
|
capture = await completeCapture({
|
|
294
273
|
scenario,
|
|
@@ -301,6 +280,8 @@ const runProcessScenario = async (scenario, policy, signal) => {
|
|
|
301
280
|
interactions,
|
|
302
281
|
exit,
|
|
303
282
|
initiallyTruncated: before.truncated,
|
|
283
|
+
eventJournal,
|
|
284
|
+
recordEvent,
|
|
304
285
|
...(signal === undefined ? {} : { signal }),
|
|
305
286
|
});
|
|
306
287
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/** Bucket one elapsed process-capture timestamp under scenario normalization. */
|
|
2
|
+
export const normalizeProcessElapsedTime = (elapsedMs, timeBucketMs) => Math.floor(elapsedMs / timeBucketMs) * timeBucketMs;
|
|
1
3
|
/** Normalize and redact one terminal/protocol payload under scenario rules. */
|
|
2
4
|
export const normalizeProcessText = (value, scenario, temporaryRoot, pid) => {
|
|
3
5
|
let normalized = value;
|
|
@@ -42,8 +44,7 @@ export const normalizeProcessSamples = (samples, scenario, rootPid) => {
|
|
|
42
44
|
: normalized;
|
|
43
45
|
};
|
|
44
46
|
return samples.map((sample) => ({
|
|
45
|
-
at_ms:
|
|
46
|
-
scenario.normalization.time_bucket_ms,
|
|
47
|
+
at_ms: normalizeProcessElapsedTime(sample.at_ms, scenario.normalization.time_bucket_ms),
|
|
47
48
|
pid: mapping.get(sample.pid) ?? 1,
|
|
48
49
|
parent_pid: mapping.get(sample.parent_pid) ?? 0,
|
|
49
50
|
process_group_id: sample.process_group_id === null
|
|
@@ -53,6 +54,12 @@ export const normalizeProcessSamples = (samples, scenario, rootPid) => {
|
|
|
53
54
|
command: normalizeCommand(sample.command),
|
|
54
55
|
}));
|
|
55
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
|
+
});
|
|
56
63
|
const normalizePidTokens = (value, identifiers) => {
|
|
57
64
|
const tokens = [...new Set(identifiers)]
|
|
58
65
|
.filter((identifier) => Number.isSafeInteger(identifier) && identifier > 0)
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { commitProcessReactiveProposal, createProcessReactiveSnapshot, reduceProcessReactiveScenario, } from "../domain/processReactiveRuntime.js";
|
|
2
|
+
const systemTimerHost = {
|
|
3
|
+
schedule: (callback, delayMs) => {
|
|
4
|
+
const timer = setTimeout(callback, delayMs);
|
|
5
|
+
return { cancel: () => clearTimeout(timer) };
|
|
6
|
+
},
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Serializes live observations, deadlines, and effect commits for one run.
|
|
10
|
+
* Producer callbacks enqueue work without awaiting it, preventing reentrancy.
|
|
11
|
+
*/
|
|
12
|
+
export class ProcessReactiveCoordinator {
|
|
13
|
+
#scenario;
|
|
14
|
+
#executor;
|
|
15
|
+
#timerHost;
|
|
16
|
+
#now;
|
|
17
|
+
#scenarioDeadlineAt;
|
|
18
|
+
#onDecision;
|
|
19
|
+
#snapshot;
|
|
20
|
+
#pendingInputs = [];
|
|
21
|
+
#tail = Promise.resolve();
|
|
22
|
+
#running = false;
|
|
23
|
+
#failure;
|
|
24
|
+
#stateTimer;
|
|
25
|
+
#scenarioTimer;
|
|
26
|
+
#stateDeadlineAt = 0;
|
|
27
|
+
#effectAbort;
|
|
28
|
+
#accepting = true;
|
|
29
|
+
#closed = false;
|
|
30
|
+
constructor(options) {
|
|
31
|
+
this.#scenario = options.scenario;
|
|
32
|
+
this.#executor = options.executor;
|
|
33
|
+
this.#timerHost = options.timerHost ?? systemTimerHost;
|
|
34
|
+
this.#now = options.now ?? Date.now;
|
|
35
|
+
this.#scenarioDeadlineAt = this.#now() + options.scenario.deadline_ms;
|
|
36
|
+
this.#onDecision = options.onDecision;
|
|
37
|
+
this.#snapshot = createProcessReactiveSnapshot(options.scenario);
|
|
38
|
+
this.#scheduleScenarioDeadline();
|
|
39
|
+
this.#scheduleStateDeadline();
|
|
40
|
+
}
|
|
41
|
+
/** Current immutable reducer snapshot after all completed turns. */
|
|
42
|
+
get snapshot() {
|
|
43
|
+
return this.#snapshot;
|
|
44
|
+
}
|
|
45
|
+
/** Enqueue producer work without creating a floating promise. */
|
|
46
|
+
enqueue(input) {
|
|
47
|
+
if (!this.#accepting)
|
|
48
|
+
return;
|
|
49
|
+
if (this.#interruptsEffects(input))
|
|
50
|
+
this.#effectAbort?.abort();
|
|
51
|
+
this.#enqueueOwned(input);
|
|
52
|
+
}
|
|
53
|
+
/** Submit one input and wait until every turn queued before it completes. */
|
|
54
|
+
async submit(input) {
|
|
55
|
+
this.enqueue(input);
|
|
56
|
+
await this.drain();
|
|
57
|
+
return this.#snapshot;
|
|
58
|
+
}
|
|
59
|
+
/** Wait for currently queued reducer/effect work and surface host failures. */
|
|
60
|
+
async drain() {
|
|
61
|
+
for (;;) {
|
|
62
|
+
const observedTail = this.#tail;
|
|
63
|
+
await observedTail;
|
|
64
|
+
if (observedTail === this.#tail)
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
if (this.#failure !== undefined)
|
|
68
|
+
throw this.#failure;
|
|
69
|
+
}
|
|
70
|
+
/** Stop owned deadlines after queued work settles. */
|
|
71
|
+
async close() {
|
|
72
|
+
this.#accepting = false;
|
|
73
|
+
this.#clearTimers();
|
|
74
|
+
await this.drain();
|
|
75
|
+
this.#closed = true;
|
|
76
|
+
}
|
|
77
|
+
#enqueueOwned(input) {
|
|
78
|
+
if (this.#closed || this.#failure !== undefined)
|
|
79
|
+
return;
|
|
80
|
+
this.#pendingInputs.push(input);
|
|
81
|
+
this.#startWorker();
|
|
82
|
+
}
|
|
83
|
+
#startWorker() {
|
|
84
|
+
if (this.#running || this.#pendingInputs.length === 0)
|
|
85
|
+
return;
|
|
86
|
+
this.#running = true;
|
|
87
|
+
this.#tail = this.#runQueue()
|
|
88
|
+
.catch((cause) => this.#fail(cause))
|
|
89
|
+
.finally(() => {
|
|
90
|
+
this.#running = false;
|
|
91
|
+
if (this.#pendingInputs.length > 0)
|
|
92
|
+
this.#startWorker();
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
async #runQueue() {
|
|
96
|
+
for (;;) {
|
|
97
|
+
const input = this.#nextInput();
|
|
98
|
+
if (input === undefined)
|
|
99
|
+
return;
|
|
100
|
+
await this.#advance(input);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
#nextInput() {
|
|
104
|
+
const terminalIndex = this.#pendingInputs.findIndex(({ kind }) => kind === "cancelled" || kind === "cleanup_failed");
|
|
105
|
+
if (terminalIndex >= 0)
|
|
106
|
+
return this.#pendingInputs.splice(terminalIndex, 1)[0];
|
|
107
|
+
let observationIndex = -1;
|
|
108
|
+
let captureOrder = Number.POSITIVE_INFINITY;
|
|
109
|
+
for (const [index, input] of this.#pendingInputs.entries())
|
|
110
|
+
if (input.kind === "observation" &&
|
|
111
|
+
input.observation.capture_order < captureOrder) {
|
|
112
|
+
observationIndex = index;
|
|
113
|
+
captureOrder = input.observation.capture_order;
|
|
114
|
+
}
|
|
115
|
+
return observationIndex >= 0
|
|
116
|
+
? this.#pendingInputs.splice(observationIndex, 1)[0]
|
|
117
|
+
: this.#pendingInputs.shift();
|
|
118
|
+
}
|
|
119
|
+
#fail(cause) {
|
|
120
|
+
this.#failure ??= cause;
|
|
121
|
+
this.#accepting = false;
|
|
122
|
+
this.#closed = true;
|
|
123
|
+
this.#effectAbort?.abort();
|
|
124
|
+
this.#pendingInputs.length = 0;
|
|
125
|
+
this.#clearTimers();
|
|
126
|
+
}
|
|
127
|
+
async #advance(input) {
|
|
128
|
+
const before = this.#snapshot;
|
|
129
|
+
let decision = reduceProcessReactiveScenario(this.#scenario, before, input);
|
|
130
|
+
let effectResults = [];
|
|
131
|
+
if (decision.kind === "proposal") {
|
|
132
|
+
const effectAbort = new AbortController();
|
|
133
|
+
this.#effectAbort = effectAbort;
|
|
134
|
+
try {
|
|
135
|
+
effectResults = await this.#executor.execute(decision.effects, effectAbort.signal);
|
|
136
|
+
}
|
|
137
|
+
catch (cause) {
|
|
138
|
+
if (effectAbort.signal.aborted)
|
|
139
|
+
return;
|
|
140
|
+
throw cause;
|
|
141
|
+
}
|
|
142
|
+
finally {
|
|
143
|
+
if (this.#effectAbort === effectAbort)
|
|
144
|
+
this.#effectAbort = undefined;
|
|
145
|
+
}
|
|
146
|
+
if (effectAbort.signal.aborted)
|
|
147
|
+
return;
|
|
148
|
+
const expired = this.#expiredDeadlineInput();
|
|
149
|
+
if (expired !== undefined) {
|
|
150
|
+
this.#enqueueOwned(expired);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
decision = commitProcessReactiveProposal(this.#scenario, decision, effectResults);
|
|
154
|
+
}
|
|
155
|
+
this.#snapshot = decision.snapshot;
|
|
156
|
+
this.#onDecision?.(decision, input);
|
|
157
|
+
if (this.#snapshot.status === "finished")
|
|
158
|
+
this.#clearTimers();
|
|
159
|
+
else if (this.#accepting &&
|
|
160
|
+
(before.active_state !== this.#snapshot.active_state ||
|
|
161
|
+
before.state_entry_capture_order !==
|
|
162
|
+
this.#snapshot.state_entry_capture_order))
|
|
163
|
+
this.#scheduleStateDeadline();
|
|
164
|
+
for (const result of effectResults)
|
|
165
|
+
if (result.status === "succeeded" &&
|
|
166
|
+
!this.#hasObservation(result.observation.event_id))
|
|
167
|
+
this.#enqueueOwned({
|
|
168
|
+
kind: "observation",
|
|
169
|
+
observation: result.observation,
|
|
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
|
+
}
|
|
177
|
+
#interruptsEffects(input) {
|
|
178
|
+
if (input.kind === "cancelled" ||
|
|
179
|
+
input.kind === "cleanup_failed" ||
|
|
180
|
+
input.kind === "scenario_deadline" ||
|
|
181
|
+
input.kind === "target_lost")
|
|
182
|
+
return true;
|
|
183
|
+
return (input.kind === "state_deadline" &&
|
|
184
|
+
input.state_id === this.#snapshot.active_state &&
|
|
185
|
+
input.state_entry_capture_order ===
|
|
186
|
+
this.#snapshot.state_entry_capture_order);
|
|
187
|
+
}
|
|
188
|
+
#expiredDeadlineInput() {
|
|
189
|
+
const now = this.#now();
|
|
190
|
+
if (now >= this.#scenarioDeadlineAt)
|
|
191
|
+
return { kind: "scenario_deadline" };
|
|
192
|
+
if (now < this.#stateDeadlineAt)
|
|
193
|
+
return undefined;
|
|
194
|
+
return {
|
|
195
|
+
kind: "state_deadline",
|
|
196
|
+
state_id: this.#snapshot.active_state,
|
|
197
|
+
state_entry_capture_order: this.#snapshot.state_entry_capture_order,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
#scheduleScenarioDeadline() {
|
|
201
|
+
if (!this.#accepting)
|
|
202
|
+
return;
|
|
203
|
+
this.#scenarioTimer = this.#timerHost.schedule(() => this.enqueue({ kind: "scenario_deadline" }), this.#scenario.deadline_ms);
|
|
204
|
+
}
|
|
205
|
+
#scheduleStateDeadline() {
|
|
206
|
+
if (!this.#accepting)
|
|
207
|
+
return;
|
|
208
|
+
this.#stateTimer?.cancel();
|
|
209
|
+
const state = this.#scenario.states.find(({ id }) => id === this.#snapshot.active_state);
|
|
210
|
+
if (state === undefined)
|
|
211
|
+
return;
|
|
212
|
+
this.#stateDeadlineAt = this.#now() + state.deadline_ms;
|
|
213
|
+
const identity = {
|
|
214
|
+
state_id: state.id,
|
|
215
|
+
state_entry_capture_order: this.#snapshot.state_entry_capture_order,
|
|
216
|
+
};
|
|
217
|
+
this.#stateTimer = this.#timerHost.schedule(() => this.enqueue({ kind: "state_deadline", ...identity }), state.deadline_ms);
|
|
218
|
+
}
|
|
219
|
+
#clearTimers() {
|
|
220
|
+
this.#stateTimer?.cancel();
|
|
221
|
+
this.#scenarioTimer?.cancel();
|
|
222
|
+
this.#stateTimer = undefined;
|
|
223
|
+
this.#scenarioTimer = undefined;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { createProcessObservation } from "../domain/processObservation.js";
|
|
2
|
+
const elapsed = (host) => Math.max(0, host.now() - host.startedAtMs);
|
|
3
|
+
const interactionData = (action) => {
|
|
4
|
+
if (action.type === "send_input")
|
|
5
|
+
return action.sensitive
|
|
6
|
+
? `<redacted-input:${String(Buffer.byteLength(action.data))}-bytes>`
|
|
7
|
+
: action.data;
|
|
8
|
+
if (action.type === "resize")
|
|
9
|
+
return `${String(action.columns)}x${String(action.rows)}`;
|
|
10
|
+
return action.signal;
|
|
11
|
+
};
|
|
12
|
+
const interactionType = (action) => {
|
|
13
|
+
if (action.type === "send_input")
|
|
14
|
+
return "input";
|
|
15
|
+
if (action.type === "resize")
|
|
16
|
+
return "resize";
|
|
17
|
+
return "signal";
|
|
18
|
+
};
|
|
19
|
+
const performTerminalAction = (terminal, renderer, action, atMs) => {
|
|
20
|
+
if (action.type === "send_input")
|
|
21
|
+
terminal.write(action.data);
|
|
22
|
+
else if (action.type === "resize") {
|
|
23
|
+
terminal.resize(action.columns, action.rows);
|
|
24
|
+
renderer.resize(action.columns, action.rows, atMs);
|
|
25
|
+
}
|
|
26
|
+
else
|
|
27
|
+
terminal.kill(action.signal);
|
|
28
|
+
};
|
|
29
|
+
const recordInteraction = (host, action, atMs, outcome) => {
|
|
30
|
+
const index = host.interactions.length;
|
|
31
|
+
const event = {
|
|
32
|
+
sequence: index,
|
|
33
|
+
scheduled_at_ms: atMs,
|
|
34
|
+
dispatched_at_ms: atMs,
|
|
35
|
+
type: interactionType(action),
|
|
36
|
+
data: interactionData(action),
|
|
37
|
+
outcome,
|
|
38
|
+
};
|
|
39
|
+
host.interactions.push(event);
|
|
40
|
+
const location = host.journal.record("interaction_events", index);
|
|
41
|
+
if (outcome !== "dispatched")
|
|
42
|
+
return { status: "rejected" };
|
|
43
|
+
return {
|
|
44
|
+
status: "succeeded",
|
|
45
|
+
observation: createProcessObservation({
|
|
46
|
+
source: "interaction",
|
|
47
|
+
source_sequence: index,
|
|
48
|
+
captured_at_ms: atMs,
|
|
49
|
+
subject_id: null,
|
|
50
|
+
location,
|
|
51
|
+
payload: event,
|
|
52
|
+
}),
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
const executeTerminalAction = (host, action) => {
|
|
56
|
+
if (action.type === "send_signal" && action.target.kind !== "root")
|
|
57
|
+
return { status: "rejected" };
|
|
58
|
+
const terminal = host.terminal();
|
|
59
|
+
if (terminal === undefined)
|
|
60
|
+
return { status: "target_lost" };
|
|
61
|
+
const atMs = elapsed(host);
|
|
62
|
+
let outcome = "dispatched";
|
|
63
|
+
try {
|
|
64
|
+
performTerminalAction(terminal, host.renderer, action, atMs);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
outcome = "failed";
|
|
68
|
+
}
|
|
69
|
+
return recordInteraction(host, action, atMs, outcome);
|
|
70
|
+
};
|
|
71
|
+
const executeCheckpoint = async (host, action, signal) => {
|
|
72
|
+
try {
|
|
73
|
+
const { index, checkpoint } = await host.checkpoints.captureAndRead(action.name, signal);
|
|
74
|
+
const location = host.journal.entries.findLast((entry) => entry.collection === "filesystem_checkpoints" && entry.index === index);
|
|
75
|
+
if (location === undefined)
|
|
76
|
+
return { status: "rejected" };
|
|
77
|
+
return {
|
|
78
|
+
status: "succeeded",
|
|
79
|
+
observation: createProcessObservation({
|
|
80
|
+
source: "filesystem",
|
|
81
|
+
source_sequence: index,
|
|
82
|
+
captured_at_ms: checkpoint.at_ms,
|
|
83
|
+
subject_id: `checkpoint:${checkpoint.name}`,
|
|
84
|
+
location,
|
|
85
|
+
payload: checkpoint,
|
|
86
|
+
}),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return { status: "rejected" };
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
/** Execute proposed effects in order and stop after the first failure. */
|
|
94
|
+
export const executeProcessReactiveEffects = async (host, actions, signal) => {
|
|
95
|
+
const results = [];
|
|
96
|
+
for (const action of actions) {
|
|
97
|
+
if (signal.aborted)
|
|
98
|
+
return results;
|
|
99
|
+
const result = action.type === "checkpoint"
|
|
100
|
+
? await executeCheckpoint(host, action, signal)
|
|
101
|
+
: executeTerminalAction(host, action);
|
|
102
|
+
results.push(result);
|
|
103
|
+
if (result.status !== "succeeded")
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
return results;
|
|
107
|
+
};
|
|
108
|
+
const triggerSources = (trigger) => {
|
|
109
|
+
if (trigger.kind === "terminal_text")
|
|
110
|
+
return [];
|
|
111
|
+
if (trigger.kind === "event")
|
|
112
|
+
return [
|
|
113
|
+
"terminal_raw",
|
|
114
|
+
"interaction",
|
|
115
|
+
"process",
|
|
116
|
+
"filesystem",
|
|
117
|
+
"http",
|
|
118
|
+
"websocket",
|
|
119
|
+
"shim",
|
|
120
|
+
].includes(trigger.source)
|
|
121
|
+
? []
|
|
122
|
+
: [trigger.source];
|
|
123
|
+
return trigger.kind === "repeat"
|
|
124
|
+
? triggerSources(trigger.trigger)
|
|
125
|
+
: trigger.triggers.flatMap(triggerSources);
|
|
126
|
+
};
|
|
127
|
+
/** Return unsupported sources, selectors, and checkpoint collisions pre-launch. */
|
|
128
|
+
export const unsupportedProcessReactiveFeatures = (scenario, reservedCheckpointNames = new Set([
|
|
129
|
+
"before",
|
|
130
|
+
"after_settlement",
|
|
131
|
+
])) => [
|
|
132
|
+
...scenario.states.flatMap((state) => state.on.flatMap((transition) => triggerSources(transition.when).map((source) => `${transition.id}:source:${source}`))),
|
|
133
|
+
...scenario.states.flatMap((state) => state.on.flatMap((transition) => transition.actions.flatMap((action) => {
|
|
134
|
+
if (action.type === "send_signal" && action.target.kind !== "root")
|
|
135
|
+
return [`${transition.id}:target:${action.target.kind}`];
|
|
136
|
+
if (action.type === "checkpoint" &&
|
|
137
|
+
reservedCheckpointNames.has(action.name))
|
|
138
|
+
return [`${transition.id}:checkpoint:${action.name}`];
|
|
139
|
+
return [];
|
|
140
|
+
}))),
|
|
141
|
+
];
|