rea-agents 2.1.0 → 2.3.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 +84 -18
- package/dist/application/AndroidApplicationService.js +17 -0
- package/dist/application/AppleApplicationService.js +17 -0
- package/dist/application/ApplicationWorkflowEvidenceResolver.js +162 -0
- package/dist/application/BinarySession.js +9 -2
- package/dist/application/BinarySessionStatus.js +39 -1
- package/dist/application/CapabilityInventory.js +15 -0
- package/dist/application/ClientRegistrationStatus.js +11 -4
- 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/InventoryProjectionEvidence.js +41 -0
- package/dist/application/InvestigationProviders.js +12 -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/ProcessCaptureJournal.js +95 -0
- package/dist/application/ProcessCaptureLifecycle.js +24 -13
- package/dist/application/ProcessCheckpoints.js +30 -4
- package/dist/application/ProcessCli.js +26 -4
- package/dist/application/ProcessHarness.js +20 -50
- package/dist/application/ProcessNormalization.js +3 -2
- package/dist/application/ProcessPairedExperiment.js +72 -0
- package/dist/application/ProcessReactiveCoordinator.js +218 -0
- package/dist/application/ProcessReactiveEffects.js +133 -0
- package/dist/application/ProcessReactiveObservations.js +20 -0
- package/dist/application/ProcessSampling.js +2 -0
- package/dist/application/SessionProviderRouter.js +7 -6
- package/dist/application/Setup.js +14 -13
- package/dist/application/SetupClientConfiguration.js +10 -6
- package/dist/application/SetupPlan.js +4 -3
- package/dist/application/SetupRegistrationEnvironment.js +12 -0
- package/dist/application/SetupSkill.js +44 -40
- package/dist/application/TerminalRenderer.js +3 -1
- package/dist/application/Uninstall.js +7 -1
- package/dist/application/Upgrade.js +16 -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 +84 -30
- 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 +55 -29
- package/dist/contracts/applicationToolContracts.js +54 -7
- package/dist/contracts/applicationWorkflowInputContracts.js +119 -0
- package/dist/contracts/electronToolContracts.js +73 -4
- package/dist/contracts/javascriptApplicationWorkflowExamples.js +32 -9
- package/dist/contracts/managedToolContracts.js +7 -2
- package/dist/contracts/processCaptureExample.js +1 -0
- package/dist/contracts/replayMachineExample.js +37 -0
- package/dist/contracts/sessionStatusContract.js +26 -0
- package/dist/contracts/toolContractExamples.js +2 -0
- package/dist/contracts/toolContractTypes.js +12 -1
- package/dist/contracts/toolContracts.js +8 -11
- package/dist/contracts/toolEffects.js +3 -0
- package/dist/contracts/toolOutputSchemaGroups.js +53 -9
- package/dist/contracts/toolOutputSchemaPrimitives.js +73 -32
- package/dist/doctorRuntime.js +41 -0
- package/dist/domain/analysisErrorPresentation.js +5 -3
- package/dist/domain/analysisErrorProjection.js +7 -1
- package/dist/domain/androidApplication.js +206 -0
- package/dist/domain/appleApplication.js +221 -0
- package/dist/domain/boundedCartesianProjection.js +15 -0
- package/dist/domain/changedBehavior.js +5 -4
- package/dist/domain/completionLedgerGeneration.js +190 -0
- package/dist/domain/errors.js +4 -2
- 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 +36 -0
- package/dist/domain/processCaptureValidation.js +101 -12
- package/dist/domain/processComparison.js +96 -24
- package/dist/domain/processObservation.js +153 -0
- package/dist/domain/processPairedExperiment.js +128 -0
- package/dist/domain/processReactiveCheckpointDataflow.js +32 -0
- package/dist/domain/processReactiveMatching.js +210 -0
- package/dist/domain/processReactiveRuntime.js +109 -0
- package/dist/domain/processReactiveScenario.js +361 -0
- package/dist/domain/processReactiveScenarioPreflight.js +120 -0
- package/dist/domain/processReactiveTransition.js +159 -0
- package/dist/domain/processScenario.js +12 -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 +332 -0
- package/dist/domain/replayMachineRun.js +216 -0
- package/dist/domain/replayMachineRuntime.js +294 -0
- package/dist/domain/runtimeIdentification.js +210 -0
- package/dist/domain/staticRuntimeCorrelation.js +4 -2
- package/dist/evaluation/CodexAgentEval.js +156 -0
- package/dist/generatedPackageMetadata.js +2 -2
- 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/identity.js +2 -1
- package/dist/main/transport.js +2 -0
- package/dist/mcpDoctor.js +310 -0
- package/dist/process/ProcessOwnership.js +200 -53
- package/dist/process/ProviderRunLineage.js +21 -0
- package/dist/server/createServer.js +8 -2
- package/dist/server/javascriptApplicationResult.js +106 -0
- 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/registerElectronTools.js +19 -5
- package/dist/server/registerEvidenceResources.js +7 -1
- package/dist/server/registerJavaScriptApplicationGraphResource.js +69 -0
- package/dist/server/registerManagedTools.js +18 -1
- package/dist/server/registerProcessComparisonTool.js +17 -7
- package/dist/server/registerReplayMachineTool.js +20 -0
- package/dist/server/registerSessionStatusTool.js +95 -25
- package/dist/server/registerSessionTools.js +12 -7
- package/dist/server/sessionAvailabilityPolicy.js +2 -1
- package/dist/server/toolResult.js +10 -4
- package/dist/serverIdentity.js +19 -0
- package/package.json +62 -29
- 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 +69 -228
- package/skills/reverse-engineer-anything/references/controlled-replay.md +12 -0
- package/skills/reverse-engineer-anything/references/evidence-workflows.md +33 -0
- package/skills/reverse-engineer-anything/references/javascript-applications.md +35 -0
- package/skills/reverse-engineer-anything/references/native-and-artifacts.md +36 -0
- package/skills/reverse-engineer-anything/references/runtime-observation.md +24 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** Checkpoints guaranteed on every known path entering each reactive state. */
|
|
2
|
+
export const definitelyAvailableCheckpoints = (scenario) => {
|
|
3
|
+
const available = new Map([
|
|
4
|
+
[scenario.initial_state, new Set()],
|
|
5
|
+
]);
|
|
6
|
+
let changed = true;
|
|
7
|
+
while (changed) {
|
|
8
|
+
changed = false;
|
|
9
|
+
for (const state of scenario.states) {
|
|
10
|
+
const before = available.get(state.id);
|
|
11
|
+
if (before === undefined)
|
|
12
|
+
continue;
|
|
13
|
+
for (const transition of state.on) {
|
|
14
|
+
if (transition.target.kind !== "goto")
|
|
15
|
+
continue;
|
|
16
|
+
const after = new Set(before);
|
|
17
|
+
for (const name of transition.actions.flatMap((action) => action.type === "checkpoint" ? [action.name] : []))
|
|
18
|
+
after.add(name);
|
|
19
|
+
const prior = available.get(transition.target.state);
|
|
20
|
+
const next = prior === undefined
|
|
21
|
+
? after
|
|
22
|
+
: new Set([...prior].filter((name) => after.has(name)));
|
|
23
|
+
if (prior !== undefined && setsEqual(prior, next))
|
|
24
|
+
continue;
|
|
25
|
+
available.set(transition.target.state, next);
|
|
26
|
+
changed = true;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return available;
|
|
31
|
+
};
|
|
32
|
+
const setsEqual = (left, right) => left.size === right.size && [...left].every((value) => right.has(value));
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import canonicalize from "canonicalize";
|
|
2
|
+
import { comparableProcessObservationPayload } from "./processObservation.js";
|
|
3
|
+
import { PROCESS_REACTIVE_LIMITS, } from "./processReactiveScenario.js";
|
|
4
|
+
const spendEvaluationWork = (budget, units) => {
|
|
5
|
+
if (units > budget.remaining)
|
|
6
|
+
return false;
|
|
7
|
+
budget.remaining -= units;
|
|
8
|
+
return true;
|
|
9
|
+
};
|
|
10
|
+
const noMatch = (overflow = false) => ({
|
|
11
|
+
matched: false,
|
|
12
|
+
eventIds: [],
|
|
13
|
+
consumeIds: [],
|
|
14
|
+
lastOrder: -1,
|
|
15
|
+
overflow,
|
|
16
|
+
});
|
|
17
|
+
const frontierOrder = (frontier, snapshot) => {
|
|
18
|
+
switch (frontier.kind) {
|
|
19
|
+
case "scenario_start":
|
|
20
|
+
return -1;
|
|
21
|
+
case "state_entry":
|
|
22
|
+
return snapshot.state_entry_capture_order - 1;
|
|
23
|
+
case "checkpoint":
|
|
24
|
+
return (snapshot.checkpoints.findLast(({ name }) => name === frontier.name)
|
|
25
|
+
?.capture_order ?? null);
|
|
26
|
+
case "event_id":
|
|
27
|
+
return (snapshot.observations.find(({ event_id }) => event_id === frontier.event_id)?.capture_order ?? null);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
const eligibleObservations = (frontier, context) => {
|
|
31
|
+
const frontierValue = frontierOrder(frontier, context.snapshot);
|
|
32
|
+
if (frontierValue === null)
|
|
33
|
+
return [];
|
|
34
|
+
const lowerBound = Math.max(frontierValue, context.afterOrder);
|
|
35
|
+
const consumed = new Set(context.snapshot.consumed_event_ids);
|
|
36
|
+
return context.snapshot.observations.filter(({ capture_order, event_id }) => capture_order > lowerBound &&
|
|
37
|
+
(context.upperOrder === null || capture_order <= context.upperOrder) &&
|
|
38
|
+
!consumed.has(event_id));
|
|
39
|
+
};
|
|
40
|
+
const canonicalJson = (value) => {
|
|
41
|
+
const serialized = canonicalize(value);
|
|
42
|
+
if (serialized === undefined)
|
|
43
|
+
throw new TypeError("Reactive scenario value is not canonical JSON");
|
|
44
|
+
return serialized;
|
|
45
|
+
};
|
|
46
|
+
const matchEvent = (trigger, context) => {
|
|
47
|
+
const expected = canonicalJson(comparableProcessObservationPayload(trigger.exact, trigger.ignore_fields));
|
|
48
|
+
const matches = [];
|
|
49
|
+
for (const observation of eligibleObservations(trigger.since, context)) {
|
|
50
|
+
if (!spendEvaluationWork(context.budget, 1))
|
|
51
|
+
return noMatch(true);
|
|
52
|
+
if (observation.source === trigger.source &&
|
|
53
|
+
canonicalJson(comparableProcessObservationPayload(observation.payload, trigger.ignore_fields)) === expected)
|
|
54
|
+
matches.push(observation);
|
|
55
|
+
}
|
|
56
|
+
if (matches.length < trigger.cardinality.min ||
|
|
57
|
+
matches.length > trigger.cardinality.max)
|
|
58
|
+
return noMatch();
|
|
59
|
+
const eventIds = matches.map(({ event_id }) => event_id);
|
|
60
|
+
return {
|
|
61
|
+
matched: true,
|
|
62
|
+
eventIds,
|
|
63
|
+
consumeIds: trigger.consume ? eventIds : [],
|
|
64
|
+
lastOrder: matches.at(-1)?.capture_order ?? context.afterOrder,
|
|
65
|
+
overflow: false,
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
const occurrenceRange = (haystack, needle, occurrence) => {
|
|
69
|
+
let offset = 0;
|
|
70
|
+
let start = -1;
|
|
71
|
+
for (let found = 0; found < occurrence; found += 1) {
|
|
72
|
+
start = haystack.indexOf(needle, offset);
|
|
73
|
+
if (start < 0)
|
|
74
|
+
return null;
|
|
75
|
+
offset = start + needle.length;
|
|
76
|
+
}
|
|
77
|
+
return { start, end: offset };
|
|
78
|
+
};
|
|
79
|
+
const matchTerminalText = (trigger, context) => {
|
|
80
|
+
const chunks = [];
|
|
81
|
+
for (const observation of eligibleObservations(trigger.since, context)) {
|
|
82
|
+
if (!spendEvaluationWork(context.budget, 1))
|
|
83
|
+
return noMatch(true);
|
|
84
|
+
if (observation.source === "terminal_raw" &&
|
|
85
|
+
typeof observation.payload === "object" &&
|
|
86
|
+
observation.payload !== null &&
|
|
87
|
+
!Array.isArray(observation.payload) &&
|
|
88
|
+
typeof observation.payload["data"] === "string")
|
|
89
|
+
chunks.push({ observation, text: observation.payload["data"] });
|
|
90
|
+
}
|
|
91
|
+
let rawBytes = 0;
|
|
92
|
+
for (const { text } of chunks) {
|
|
93
|
+
if (text.length > PROCESS_REACTIVE_LIMITS.terminalMatchBytes)
|
|
94
|
+
return noMatch(true);
|
|
95
|
+
const bytes = Buffer.byteLength(text);
|
|
96
|
+
rawBytes += bytes;
|
|
97
|
+
if (rawBytes > PROCESS_REACTIVE_LIMITS.terminalMatchBytes ||
|
|
98
|
+
!spendEvaluationWork(context.budget, bytes))
|
|
99
|
+
return noMatch(true);
|
|
100
|
+
}
|
|
101
|
+
const range = occurrenceRange(chunks.map(({ text }) => text).join(""), trigger.literal, trigger.occurrence);
|
|
102
|
+
if (range === null)
|
|
103
|
+
return noMatch();
|
|
104
|
+
let firstChunk = 0;
|
|
105
|
+
let lastChunk = 0;
|
|
106
|
+
let characters = 0;
|
|
107
|
+
for (const [index, chunk] of chunks.entries()) {
|
|
108
|
+
characters += chunk.text.length;
|
|
109
|
+
if (characters <= range.start)
|
|
110
|
+
firstChunk = index + 1;
|
|
111
|
+
if (characters >= range.end) {
|
|
112
|
+
lastChunk = index;
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const matched = chunks
|
|
117
|
+
.slice(firstChunk, lastChunk + 1)
|
|
118
|
+
.map(({ observation }) => observation);
|
|
119
|
+
const eventIds = matched.map(({ event_id }) => event_id);
|
|
120
|
+
return {
|
|
121
|
+
matched: true,
|
|
122
|
+
eventIds,
|
|
123
|
+
consumeIds: trigger.consume ? eventIds : [],
|
|
124
|
+
lastOrder: matched.at(-1)?.capture_order ?? context.afterOrder,
|
|
125
|
+
overflow: false,
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
const mergeMatches = (matches) => ({
|
|
129
|
+
matched: matches.every(({ matched }) => matched),
|
|
130
|
+
eventIds: [...new Set(matches.flatMap(({ eventIds }) => eventIds))],
|
|
131
|
+
consumeIds: [...new Set(matches.flatMap(({ consumeIds }) => consumeIds))],
|
|
132
|
+
lastOrder: Math.max(-1, ...matches.map(({ lastOrder }) => lastOrder)),
|
|
133
|
+
overflow: matches.some(({ overflow }) => overflow),
|
|
134
|
+
});
|
|
135
|
+
const matchEarliestPrefix = (trigger, context) => {
|
|
136
|
+
for (const observation of context.snapshot.observations) {
|
|
137
|
+
if (observation.capture_order <= context.afterOrder)
|
|
138
|
+
continue;
|
|
139
|
+
if (context.upperOrder !== null &&
|
|
140
|
+
observation.capture_order > context.upperOrder)
|
|
141
|
+
break;
|
|
142
|
+
const matched = matchTrigger(trigger, {
|
|
143
|
+
...context,
|
|
144
|
+
upperOrder: observation.capture_order,
|
|
145
|
+
});
|
|
146
|
+
if (matched.matched || matched.overflow)
|
|
147
|
+
return matched;
|
|
148
|
+
}
|
|
149
|
+
return noMatch();
|
|
150
|
+
};
|
|
151
|
+
const matchTrigger = (trigger, context) => {
|
|
152
|
+
if (trigger.kind === "event")
|
|
153
|
+
return matchEvent(trigger, context);
|
|
154
|
+
if (trigger.kind === "terminal_text")
|
|
155
|
+
return matchTerminalText(trigger, context);
|
|
156
|
+
if (trigger.kind === "all")
|
|
157
|
+
return mergeMatches(trigger.triggers.map((child) => matchTrigger(child, context)));
|
|
158
|
+
if (trigger.kind === "any") {
|
|
159
|
+
const evaluated = trigger.triggers.map((child) => matchTrigger(child, context));
|
|
160
|
+
const matches = evaluated.filter(({ matched }) => matched);
|
|
161
|
+
return matches.length === 0
|
|
162
|
+
? noMatch(evaluated.some(({ overflow }) => overflow))
|
|
163
|
+
: mergeMatches(matches);
|
|
164
|
+
}
|
|
165
|
+
if (trigger.kind === "sequence") {
|
|
166
|
+
const matches = [];
|
|
167
|
+
let cursor = context.afterOrder;
|
|
168
|
+
for (const child of trigger.triggers) {
|
|
169
|
+
const matched = matchEarliestPrefix(child, {
|
|
170
|
+
...context,
|
|
171
|
+
afterOrder: cursor,
|
|
172
|
+
});
|
|
173
|
+
if (!matched.matched)
|
|
174
|
+
return noMatch(matched.overflow);
|
|
175
|
+
matches.push(matched);
|
|
176
|
+
cursor = matched.lastOrder;
|
|
177
|
+
}
|
|
178
|
+
return mergeMatches(matches);
|
|
179
|
+
}
|
|
180
|
+
const matches = [];
|
|
181
|
+
let cursor = context.afterOrder;
|
|
182
|
+
while (matches.length < trigger.max) {
|
|
183
|
+
const matched = matchEarliestPrefix(trigger.trigger, {
|
|
184
|
+
...context,
|
|
185
|
+
afterOrder: cursor,
|
|
186
|
+
});
|
|
187
|
+
if (!matched.matched || matched.lastOrder <= cursor)
|
|
188
|
+
break;
|
|
189
|
+
matches.push(matched);
|
|
190
|
+
cursor = matched.lastOrder;
|
|
191
|
+
}
|
|
192
|
+
if (matches.length === trigger.max) {
|
|
193
|
+
const excess = matchEarliestPrefix(trigger.trigger, {
|
|
194
|
+
...context,
|
|
195
|
+
afterOrder: cursor,
|
|
196
|
+
});
|
|
197
|
+
if (excess.matched || excess.overflow)
|
|
198
|
+
return noMatch(excess.overflow);
|
|
199
|
+
}
|
|
200
|
+
if (matches.length < trigger.min)
|
|
201
|
+
return noMatch(matches.some(({ overflow }) => overflow));
|
|
202
|
+
return mergeMatches(matches);
|
|
203
|
+
};
|
|
204
|
+
/** Match one trigger using a shared deterministic work budget. */
|
|
205
|
+
export const matchProcessReactiveTrigger = (trigger, snapshot, budget) => matchTrigger(trigger, {
|
|
206
|
+
snapshot,
|
|
207
|
+
budget,
|
|
208
|
+
afterOrder: -1,
|
|
209
|
+
upperOrder: null,
|
|
210
|
+
});
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { matchProcessReactiveTrigger } from "./processReactiveMatching.js";
|
|
2
|
+
import { PROCESS_REACTIVE_LIMITS, } from "./processReactiveScenario.js";
|
|
3
|
+
export { commitProcessReactiveProposal } from "./processReactiveTransition.js";
|
|
4
|
+
const counterValue = (counters, id) => counters.find((counter) => counter.id === id)?.count ?? 0;
|
|
5
|
+
/** Create the initial immutable reducer state for one validated scenario. */
|
|
6
|
+
export const createProcessReactiveSnapshot = (scenario) => ({
|
|
7
|
+
status: "running",
|
|
8
|
+
outcome: null,
|
|
9
|
+
active_state: scenario.initial_state,
|
|
10
|
+
state_entry_capture_order: 0,
|
|
11
|
+
observations: [],
|
|
12
|
+
consumed_event_ids: [],
|
|
13
|
+
transition_uses: [],
|
|
14
|
+
state_visits: [{ id: scenario.initial_state, count: 1 }],
|
|
15
|
+
checkpoints: [],
|
|
16
|
+
transitions: [],
|
|
17
|
+
});
|
|
18
|
+
const finishedDecision = (snapshot, outcome, evaluations = []) => ({
|
|
19
|
+
kind: "finished",
|
|
20
|
+
outcome,
|
|
21
|
+
evaluations,
|
|
22
|
+
snapshot: { ...snapshot, status: "finished", outcome },
|
|
23
|
+
});
|
|
24
|
+
const reduceControlInput = (snapshot, input) => {
|
|
25
|
+
if (input.kind === "cleanup_failed")
|
|
26
|
+
return finishedDecision(snapshot, "cleanup_failed");
|
|
27
|
+
if (snapshot.status === "finished")
|
|
28
|
+
return { kind: "already_finished", snapshot, evaluations: [] };
|
|
29
|
+
if (input.kind === "observation")
|
|
30
|
+
return null;
|
|
31
|
+
if (input.kind === "state_deadline")
|
|
32
|
+
return input.state_id === snapshot.active_state &&
|
|
33
|
+
input.state_entry_capture_order === snapshot.state_entry_capture_order
|
|
34
|
+
? finishedDecision(snapshot, "predicate_timeout")
|
|
35
|
+
: { kind: "waiting", snapshot, evaluations: [] };
|
|
36
|
+
if (input.kind === "scenario_deadline")
|
|
37
|
+
return finishedDecision(snapshot, "scenario_deadline");
|
|
38
|
+
return finishedDecision(snapshot, "cancelled");
|
|
39
|
+
};
|
|
40
|
+
/** Advance one validated scenario using only an observation or explicit deadline. */
|
|
41
|
+
export const reduceProcessReactiveScenario = (scenario, snapshot, input) => {
|
|
42
|
+
const controlDecision = reduceControlInput(snapshot, input);
|
|
43
|
+
if (controlDecision !== null)
|
|
44
|
+
return controlDecision;
|
|
45
|
+
if (input.kind !== "observation")
|
|
46
|
+
return finishedDecision(snapshot, "capture_incomplete");
|
|
47
|
+
const prior = snapshot.observations.at(-1);
|
|
48
|
+
if ((prior !== undefined &&
|
|
49
|
+
input.observation.capture_order <= prior.capture_order) ||
|
|
50
|
+
snapshot.observations.some(({ event_id }) => event_id === input.observation.event_id))
|
|
51
|
+
return finishedDecision(snapshot, "capture_incomplete");
|
|
52
|
+
if (snapshot.observations.length >= PROCESS_REACTIVE_LIMITS.retainedObservations)
|
|
53
|
+
return finishedDecision(snapshot, "capture_incomplete");
|
|
54
|
+
const observed = {
|
|
55
|
+
...snapshot,
|
|
56
|
+
observations: [...snapshot.observations, input.observation],
|
|
57
|
+
};
|
|
58
|
+
const state = scenario.states.find(({ id }) => id === observed.active_state);
|
|
59
|
+
if (state === undefined)
|
|
60
|
+
return finishedDecision(observed, "target_lost");
|
|
61
|
+
const budget = { remaining: PROCESS_REACTIVE_LIMITS.evaluationWork };
|
|
62
|
+
const matches = state.on.map((transition) => ({
|
|
63
|
+
transition,
|
|
64
|
+
match: matchProcessReactiveTrigger(transition.when, observed, budget),
|
|
65
|
+
uses: counterValue(observed.transition_uses, transition.id),
|
|
66
|
+
}));
|
|
67
|
+
if (matches.some(({ match }) => match.overflow))
|
|
68
|
+
return finishedDecision(observed, "capture_incomplete");
|
|
69
|
+
const evaluations = matches.map(({ transition, match, uses }) => ({
|
|
70
|
+
transition_id: transition.id,
|
|
71
|
+
status: uses >= transition.max_uses
|
|
72
|
+
? "exhausted"
|
|
73
|
+
: match.matched
|
|
74
|
+
? "matched"
|
|
75
|
+
: "unmatched",
|
|
76
|
+
matched_event_ids: match.eventIds,
|
|
77
|
+
}));
|
|
78
|
+
const eligible = matches.filter(({ transition, match, uses }) => match.matched && uses < transition.max_uses);
|
|
79
|
+
if (eligible.length === 0)
|
|
80
|
+
return { kind: "waiting", snapshot: observed, evaluations };
|
|
81
|
+
const minimumPriority = Math.min(...eligible.map(({ transition }) => transition.priority));
|
|
82
|
+
const winners = eligible.filter(({ transition }) => transition.priority === minimumPriority);
|
|
83
|
+
if (winners.length !== 1)
|
|
84
|
+
return finishedDecision(observed, "ambiguous_match", evaluations);
|
|
85
|
+
const winner = winners[0];
|
|
86
|
+
if (winner === undefined)
|
|
87
|
+
return finishedDecision(observed, "capture_incomplete", evaluations);
|
|
88
|
+
const targetDeclaration = winner.transition.target;
|
|
89
|
+
if (targetDeclaration.kind === "goto") {
|
|
90
|
+
const target = scenario.states.find(({ id }) => id === targetDeclaration.state);
|
|
91
|
+
if (target === undefined ||
|
|
92
|
+
counterValue(observed.state_visits, target.id) >= target.max_visits)
|
|
93
|
+
return finishedDecision(observed, "capture_incomplete", evaluations);
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
kind: "proposal",
|
|
97
|
+
snapshot: observed,
|
|
98
|
+
evaluations,
|
|
99
|
+
effects: winner.transition.actions,
|
|
100
|
+
proposal: {
|
|
101
|
+
transition_id: winner.transition.id,
|
|
102
|
+
transition_identity: canonicalize(winner.transition) ?? "",
|
|
103
|
+
observation_event_id: input.observation.event_id,
|
|
104
|
+
trigger_event_ids: winner.match.eventIds,
|
|
105
|
+
consume_event_ids: winner.match.consumeIds,
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
import canonicalize from "canonicalize";
|