rea-agents 2.2.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 +56 -9
- 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/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/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/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 +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 +40 -0
- package/dist/doctorRuntime.js +41 -0
- package/dist/domain/analysisErrorPresentation.js +5 -3
- package/dist/domain/analysisErrorProjection.js +7 -1
- 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 +78 -25
- package/dist/domain/processObservation.js +153 -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 +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/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/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 +2 -2
- package/skills/reverse-engineer-anything/references/javascript-applications.md +10 -0
|
@@ -18,23 +18,24 @@ const MAX_CANONICAL_BYTES = 32 * 1024 * 1024;
|
|
|
18
18
|
const providers = {
|
|
19
19
|
behavioral: {
|
|
20
20
|
operation: "compare_process_captures",
|
|
21
|
-
|
|
21
|
+
predicates: ["rea.process-comparison/v3", "rea.process-comparison/v4"],
|
|
22
22
|
id: "rea-process",
|
|
23
23
|
name: "REA deterministic process harness",
|
|
24
24
|
},
|
|
25
25
|
"structural-function": {
|
|
26
26
|
operation: "compare_functions",
|
|
27
|
-
|
|
27
|
+
predicates: ["rea.function-comparison/v1"],
|
|
28
28
|
id: "rea-function-comparison",
|
|
29
29
|
name: "REA function comparison",
|
|
30
30
|
},
|
|
31
31
|
"structural-artifact": {
|
|
32
32
|
operation: "compare_artifacts",
|
|
33
|
-
|
|
33
|
+
predicates: ["rea.artifact-comparison/v1"],
|
|
34
34
|
id: "rea-artifact-comparison",
|
|
35
35
|
name: "REA artifact comparison",
|
|
36
36
|
},
|
|
37
37
|
};
|
|
38
|
+
const acceptsPredicate = (expected, actual) => expected.some((predicate) => predicate === actual);
|
|
38
39
|
/** Verify only explicitly declared claims; no global equivalence is inferred. */
|
|
39
40
|
export const verifyReconstruction = (specificationInput, bundleInput, offset, limit) => {
|
|
40
41
|
const specification = reconstructionSpecificationSchema.parse(specificationInput);
|
|
@@ -157,7 +158,7 @@ const validateComparisonIdentity = (claim, evidence) => {
|
|
|
157
158
|
parseEvidence(evidence);
|
|
158
159
|
const expected = providers[claim.kind];
|
|
159
160
|
if (evidence.operation !== expected.operation ||
|
|
160
|
-
evidence.predicate_type
|
|
161
|
+
!acceptsPredicate(expected.predicates, evidence.predicate_type) ||
|
|
161
162
|
evidence.provider.id !== expected.id ||
|
|
162
163
|
evidence.provider.name !== expected.name ||
|
|
163
164
|
evidence.provider.version !== (claim.kind === "behavioral" ? "3" : "1") ||
|
|
@@ -14,8 +14,17 @@ const sensitiveHeaderNames = new Set([
|
|
|
14
14
|
"proxy-authorization",
|
|
15
15
|
"set-cookie",
|
|
16
16
|
]);
|
|
17
|
+
const headerNameSchema = z
|
|
18
|
+
.string()
|
|
19
|
+
.min(1)
|
|
20
|
+
.max(256)
|
|
21
|
+
.regex(/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/u);
|
|
22
|
+
const headerValueSchema = z
|
|
23
|
+
.string()
|
|
24
|
+
.max(8_192)
|
|
25
|
+
.regex(/^[\t\x20-\x7e\x80-\xff]*$/u);
|
|
17
26
|
const replayHeaderMatchersSchema = z
|
|
18
|
-
.record(
|
|
27
|
+
.record(headerNameSchema, headerValueSchema)
|
|
19
28
|
.superRefine((headers, context) => {
|
|
20
29
|
const names = Object.keys(headers).map((name) => name.toLowerCase());
|
|
21
30
|
if (new Set(names).size !== names.length)
|
|
@@ -77,7 +86,7 @@ const replayActionSchema = z.discriminatedUnion("type", [
|
|
|
77
86
|
z.strictObject({
|
|
78
87
|
type: z.literal("http_response"),
|
|
79
88
|
status: z.number().int().min(100).max(599),
|
|
80
|
-
headers: z.record(
|
|
89
|
+
headers: z.record(headerNameSchema, headerValueSchema).default({}),
|
|
81
90
|
body: z.string().max(1_000_000),
|
|
82
91
|
}),
|
|
83
92
|
z.strictObject({
|
|
@@ -189,6 +198,13 @@ const validateReplayVariables = (machine, context) => {
|
|
|
189
198
|
const capturedVariables = new Map();
|
|
190
199
|
for (const [transitionIndex, transition] of machine.transitions.entries()) {
|
|
191
200
|
for (const capture of transition.captures) {
|
|
201
|
+
if (capture.sensitive && capture.value.source === "action_json")
|
|
202
|
+
context.addIssue({
|
|
203
|
+
code: "custom",
|
|
204
|
+
message: "sensitive replay captures cannot read literal action JSON persisted in the replay plan",
|
|
205
|
+
path: ["transitions", transitionIndex, "captures"],
|
|
206
|
+
input: machine,
|
|
207
|
+
});
|
|
192
208
|
const sensitivity = capturedVariables.get(capture.variable);
|
|
193
209
|
if (sensitivity !== undefined && sensitivity !== capture.sensitive)
|
|
194
210
|
context.addIssue({
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { replayMachineSchema } from "./replayMachine.js";
|
|
3
|
+
import { replayMachineEventSchema, ReplayMachineRuntime, } from "./replayMachineRuntime.js";
|
|
4
|
+
const refusalOutcomeSchema = z.enum([
|
|
5
|
+
"unmatched",
|
|
6
|
+
"invalid_state",
|
|
7
|
+
"guard_failed",
|
|
8
|
+
"transition_exhausted",
|
|
9
|
+
"invalid_capture",
|
|
10
|
+
"unexpected_reconnect",
|
|
11
|
+
"limit_exhausted",
|
|
12
|
+
]);
|
|
13
|
+
const MAX_DIRECT_REPLAY_EVENTS = 10_000;
|
|
14
|
+
const MAX_DIRECT_REPLAY_ACTION_BYTES = 4 * 1_024 * 1_024;
|
|
15
|
+
const MAX_DIRECT_REPLAY_ACTION_FIELDS = 1_024;
|
|
16
|
+
const MAX_DIRECT_REPLAY_SENSITIVE_CAPTURES = 1_024;
|
|
17
|
+
const actionFieldCount = (machine) => machine.transitions.reduce((total, transition) => total +
|
|
18
|
+
transition.actions.reduce((fields, action) => fields +
|
|
19
|
+
(action.type === "http_response"
|
|
20
|
+
? Object.keys(action.headers).length + 1
|
|
21
|
+
: action.type === "websocket_send"
|
|
22
|
+
? 1
|
|
23
|
+
: 0), 0), 0);
|
|
24
|
+
const maximumSensitiveCapturesPerEvent = (machine) => Math.max(0, ...machine.transitions.map(({ captures }) => captures.filter(({ sensitive }) => sensitive).length));
|
|
25
|
+
/** Complete declarative input for one direct replay-machine run. */
|
|
26
|
+
export const replayMachineRunInputSchema = z
|
|
27
|
+
.strictObject({
|
|
28
|
+
machine: replayMachineSchema,
|
|
29
|
+
events: z.array(replayMachineEventSchema).max(MAX_DIRECT_REPLAY_EVENTS),
|
|
30
|
+
})
|
|
31
|
+
.superRefine(({ machine, events }, context) => {
|
|
32
|
+
const actionBytes = Buffer.byteLength(JSON.stringify(machine.transitions.map(({ id, actions }) => ({ id, actions }))));
|
|
33
|
+
if (actionBytes > MAX_DIRECT_REPLAY_ACTION_BYTES)
|
|
34
|
+
context.addIssue({
|
|
35
|
+
code: "custom",
|
|
36
|
+
path: ["machine", "transitions"],
|
|
37
|
+
message: "direct replay actions exceed the 4 MiB output boundary",
|
|
38
|
+
input: machine.transitions,
|
|
39
|
+
});
|
|
40
|
+
if (actionFieldCount(machine) > MAX_DIRECT_REPLAY_ACTION_FIELDS)
|
|
41
|
+
context.addIssue({
|
|
42
|
+
code: "custom",
|
|
43
|
+
path: ["machine", "transitions"],
|
|
44
|
+
message: "direct replay actions exceed the 1,024-field output boundary",
|
|
45
|
+
input: machine.transitions,
|
|
46
|
+
});
|
|
47
|
+
if (maximumSensitiveCapturesPerEvent(machine) * events.length >
|
|
48
|
+
MAX_DIRECT_REPLAY_SENSITIVE_CAPTURES)
|
|
49
|
+
context.addIssue({
|
|
50
|
+
code: "custom",
|
|
51
|
+
path: ["events"],
|
|
52
|
+
message: "direct replay events exceed the 1,024 sensitive-capture operation boundary",
|
|
53
|
+
input: events,
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
const replayMachineRunDecisionSchema = z.strictObject({
|
|
57
|
+
event_sequence: z.number().int().nonnegative(),
|
|
58
|
+
outcome: z.union([z.literal("matched"), refusalOutcomeSchema]),
|
|
59
|
+
transition_sequence: z.number().int().nonnegative().nullable(),
|
|
60
|
+
});
|
|
61
|
+
const replayRunActionSchema = z.discriminatedUnion("type", [
|
|
62
|
+
z.strictObject({
|
|
63
|
+
type: z.literal("http_response"),
|
|
64
|
+
status: z.number().int().min(100).max(599),
|
|
65
|
+
headers: z.record(z
|
|
66
|
+
.string()
|
|
67
|
+
.min(1)
|
|
68
|
+
.max(256)
|
|
69
|
+
.regex(/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/u), z.string().max(8_192)),
|
|
70
|
+
body: z.string().max(1_000_000),
|
|
71
|
+
}),
|
|
72
|
+
z.strictObject({
|
|
73
|
+
type: z.literal("websocket_send"),
|
|
74
|
+
data: z.string().max(1_000_000),
|
|
75
|
+
}),
|
|
76
|
+
z.strictObject({ type: z.literal("disconnect") }),
|
|
77
|
+
z.strictObject({
|
|
78
|
+
type: z.literal("delay"),
|
|
79
|
+
duration_ms: z.number().int().nonnegative().max(30_000),
|
|
80
|
+
}),
|
|
81
|
+
]);
|
|
82
|
+
const replayRunIdentifierSchema = z
|
|
83
|
+
.string()
|
|
84
|
+
.regex(/^[A-Za-z][A-Za-z0-9_-]{0,63}$/u);
|
|
85
|
+
const replayMachineRunJournalEntrySchema = z.strictObject({
|
|
86
|
+
sequence: z.number().int().nonnegative(),
|
|
87
|
+
at_ms: z.number().int().nonnegative(),
|
|
88
|
+
transition_id: replayRunIdentifierSchema,
|
|
89
|
+
state_before: replayRunIdentifierSchema,
|
|
90
|
+
state_after: replayRunIdentifierSchema,
|
|
91
|
+
captured_aliases: z
|
|
92
|
+
.array(z.strictObject({
|
|
93
|
+
name: replayRunIdentifierSchema,
|
|
94
|
+
sensitive: z.boolean(),
|
|
95
|
+
}))
|
|
96
|
+
.max(32),
|
|
97
|
+
});
|
|
98
|
+
/** Capture-value-free result of directly evaluating a finite replay machine. */
|
|
99
|
+
export const replayMachineRunOutputSchema = z.strictObject({
|
|
100
|
+
schema_version: z.literal(1),
|
|
101
|
+
initial_state: replayRunIdentifierSchema,
|
|
102
|
+
final_state: replayRunIdentifierSchema,
|
|
103
|
+
terminal: z.boolean(),
|
|
104
|
+
decisions: z
|
|
105
|
+
.array(replayMachineRunDecisionSchema)
|
|
106
|
+
.max(MAX_DIRECT_REPLAY_EVENTS),
|
|
107
|
+
transition_journal: z
|
|
108
|
+
.array(replayMachineRunJournalEntrySchema)
|
|
109
|
+
.max(MAX_DIRECT_REPLAY_EVENTS),
|
|
110
|
+
transition_actions: z
|
|
111
|
+
.array(z.strictObject({
|
|
112
|
+
transition_id: replayRunIdentifierSchema,
|
|
113
|
+
actions: z.array(replayRunActionSchema).max(32),
|
|
114
|
+
}))
|
|
115
|
+
.max(2_000),
|
|
116
|
+
limits: z.strictObject({
|
|
117
|
+
configured: z.strictObject({
|
|
118
|
+
transitions: z.number().int().positive(),
|
|
119
|
+
connections: z.number().int().positive(),
|
|
120
|
+
messages: z.number().int().positive(),
|
|
121
|
+
bytes: z.number().int().positive(),
|
|
122
|
+
duration_ms: z.number().int().positive(),
|
|
123
|
+
}),
|
|
124
|
+
usage: z.strictObject({
|
|
125
|
+
offered_events: z.number().int().nonnegative(),
|
|
126
|
+
admitted_events: z.number().int().nonnegative(),
|
|
127
|
+
transitions: z.number().int().nonnegative(),
|
|
128
|
+
connections: z.number().int().nonnegative(),
|
|
129
|
+
messages: z.number().int().nonnegative(),
|
|
130
|
+
bytes: z.number().int().nonnegative(),
|
|
131
|
+
last_at_ms: z.number().int().nonnegative(),
|
|
132
|
+
}),
|
|
133
|
+
}),
|
|
134
|
+
});
|
|
135
|
+
const redactAction = (runtime, action) => {
|
|
136
|
+
switch (action.type) {
|
|
137
|
+
case "http_response":
|
|
138
|
+
return {
|
|
139
|
+
...action,
|
|
140
|
+
headers: Object.fromEntries(Object.entries(action.headers).map(([name, value]) => [
|
|
141
|
+
name,
|
|
142
|
+
runtime.redactField(value),
|
|
143
|
+
])),
|
|
144
|
+
body: runtime.redactField(action.body),
|
|
145
|
+
};
|
|
146
|
+
case "websocket_send":
|
|
147
|
+
return { ...action, data: runtime.redactField(action.data) };
|
|
148
|
+
case "delay":
|
|
149
|
+
case "disconnect":
|
|
150
|
+
return action;
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
/** Evaluate every supplied event in order without opening sockets or processes. */
|
|
154
|
+
export const runReplayMachine = (input) => {
|
|
155
|
+
const runtime = new ReplayMachineRuntime(input.machine);
|
|
156
|
+
const decisions = [];
|
|
157
|
+
const transitionJournal = [];
|
|
158
|
+
const usedTransitionIds = new Set();
|
|
159
|
+
for (const [eventSequence, event] of input.events.entries()) {
|
|
160
|
+
const decision = runtime.dispatch(event);
|
|
161
|
+
decisions.push({
|
|
162
|
+
event_sequence: eventSequence,
|
|
163
|
+
outcome: decision.outcome,
|
|
164
|
+
transition_sequence: decision.transition?.sequence ?? null,
|
|
165
|
+
});
|
|
166
|
+
if (decision.outcome !== "matched")
|
|
167
|
+
continue;
|
|
168
|
+
usedTransitionIds.add(decision.transition.transition_id);
|
|
169
|
+
const definition = input.machine.transitions.find(({ id }) => id === decision.transition.transition_id);
|
|
170
|
+
if (definition === undefined)
|
|
171
|
+
throw new TypeError("Replay runtime selected an unknown transition");
|
|
172
|
+
transitionJournal.push({
|
|
173
|
+
sequence: decision.transition.sequence,
|
|
174
|
+
at_ms: decision.transition.at_ms,
|
|
175
|
+
transition_id: decision.transition.transition_id,
|
|
176
|
+
state_before: decision.transition.state_before,
|
|
177
|
+
state_after: decision.transition.state_after,
|
|
178
|
+
captured_aliases: definition.captures.map(({ variable, sensitive }) => ({
|
|
179
|
+
name: variable,
|
|
180
|
+
sensitive,
|
|
181
|
+
})),
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
const usage = runtime.usage;
|
|
185
|
+
const transitionActions = input.machine.transitions
|
|
186
|
+
.filter(({ id }) => usedTransitionIds.has(id))
|
|
187
|
+
.map(({ id, actions }) => ({
|
|
188
|
+
transition_id: id,
|
|
189
|
+
actions: actions.map((action) => redactAction(runtime, action)),
|
|
190
|
+
}));
|
|
191
|
+
return replayMachineRunOutputSchema.parse({
|
|
192
|
+
schema_version: 1,
|
|
193
|
+
initial_state: input.machine.initial_state,
|
|
194
|
+
final_state: runtime.state,
|
|
195
|
+
terminal: input.machine.states.find(({ name }) => name === runtime.state)
|
|
196
|
+
?.terminal ?? false,
|
|
197
|
+
decisions,
|
|
198
|
+
transition_journal: transitionJournal,
|
|
199
|
+
transition_actions: transitionActions,
|
|
200
|
+
limits: {
|
|
201
|
+
configured: {
|
|
202
|
+
transitions: input.machine.max_transitions,
|
|
203
|
+
...input.machine.limits,
|
|
204
|
+
},
|
|
205
|
+
usage: {
|
|
206
|
+
offered_events: input.events.length,
|
|
207
|
+
admitted_events: usage.events,
|
|
208
|
+
transitions: usage.transitions,
|
|
209
|
+
connections: usage.connections,
|
|
210
|
+
messages: usage.messages,
|
|
211
|
+
bytes: usage.bytes,
|
|
212
|
+
last_at_ms: usage.last_at_ms,
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
};
|
|
@@ -1,4 +1,54 @@
|
|
|
1
1
|
import canonicalize from "canonicalize";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
/** One bounded inbound protocol event offered to a replay machine. */
|
|
4
|
+
const replayEventHeadersSchema = z
|
|
5
|
+
.record(z
|
|
6
|
+
.string()
|
|
7
|
+
.min(1)
|
|
8
|
+
.max(256)
|
|
9
|
+
.regex(/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/u), z
|
|
10
|
+
.string()
|
|
11
|
+
.max(8_192)
|
|
12
|
+
.regex(/^[\t\x20-\x7e\x80-\xff]*$/u))
|
|
13
|
+
.superRefine((headers, context) => {
|
|
14
|
+
const normalized = Object.keys(headers).map((name) => name.toLowerCase());
|
|
15
|
+
if (new Set(normalized).size !== normalized.length)
|
|
16
|
+
context.addIssue({
|
|
17
|
+
code: "custom",
|
|
18
|
+
message: "replay event headers must be unique ignoring case",
|
|
19
|
+
input: headers,
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
const replayEventFields = {
|
|
23
|
+
at_ms: z.number().int().nonnegative().safe(),
|
|
24
|
+
path: z.string().startsWith("/").max(8_192),
|
|
25
|
+
headers: replayEventHeadersSchema,
|
|
26
|
+
body: z.string().max(1_000_000),
|
|
27
|
+
};
|
|
28
|
+
export const replayMachineEventSchema = z.discriminatedUnion("protocol", [
|
|
29
|
+
z.strictObject({
|
|
30
|
+
protocol: z.literal("http"),
|
|
31
|
+
connection: z.literal("not_applicable"),
|
|
32
|
+
method: z
|
|
33
|
+
.string()
|
|
34
|
+
.min(1)
|
|
35
|
+
.max(16)
|
|
36
|
+
.transform((method) => method.toUpperCase()),
|
|
37
|
+
...replayEventFields,
|
|
38
|
+
}),
|
|
39
|
+
z.strictObject({
|
|
40
|
+
protocol: z.literal("websocket_connect"),
|
|
41
|
+
connection: z.enum(["initial", "reconnect"]),
|
|
42
|
+
method: z.null(),
|
|
43
|
+
...replayEventFields,
|
|
44
|
+
}),
|
|
45
|
+
z.strictObject({
|
|
46
|
+
protocol: z.literal("websocket_message"),
|
|
47
|
+
connection: z.enum(["initial", "reconnect"]),
|
|
48
|
+
method: z.null(),
|
|
49
|
+
...replayEventFields,
|
|
50
|
+
}),
|
|
51
|
+
]);
|
|
2
52
|
const parseJson = (value) => {
|
|
3
53
|
try {
|
|
4
54
|
return JSON.parse(value);
|
|
@@ -76,6 +126,17 @@ const isJsonCompatible = (value) => {
|
|
|
76
126
|
const replayValuesEqual = (left, right) => isJsonCompatible(left) &&
|
|
77
127
|
isJsonCompatible(right) &&
|
|
78
128
|
(Object.is(left, right) || canonicalize(left) === canonicalize(right));
|
|
129
|
+
const createTransitionRecord = ({ sequence, atMs, stateBefore, transition, captures, }) => ({
|
|
130
|
+
sequence,
|
|
131
|
+
at_ms: atMs,
|
|
132
|
+
transition_id: transition.id,
|
|
133
|
+
state_before: stateBefore,
|
|
134
|
+
state_after: transition.to,
|
|
135
|
+
sensitive_aliases: captures
|
|
136
|
+
.filter(({ sensitive }) => sensitive)
|
|
137
|
+
.map(({ variable }) => variable)
|
|
138
|
+
.sort(),
|
|
139
|
+
});
|
|
79
140
|
/** Stateful evaluator for one validated replay-machine instance. */
|
|
80
141
|
export class ReplayMachineRuntime {
|
|
81
142
|
machine;
|
|
@@ -89,6 +150,7 @@ export class ReplayMachineRuntime {
|
|
|
89
150
|
#messages = 0;
|
|
90
151
|
#bytes = 0;
|
|
91
152
|
#lastAtMs = 0;
|
|
153
|
+
#events = 0;
|
|
92
154
|
constructor(machine) {
|
|
93
155
|
this.machine = machine;
|
|
94
156
|
this.#state = machine.initial_state;
|
|
@@ -102,6 +164,17 @@ export class ReplayMachineRuntime {
|
|
|
102
164
|
get timeline() {
|
|
103
165
|
return this.#timeline;
|
|
104
166
|
}
|
|
167
|
+
/** Current committed use of the machine's declared limits. */
|
|
168
|
+
get usage() {
|
|
169
|
+
return {
|
|
170
|
+
events: this.#events,
|
|
171
|
+
transitions: this.#timeline.length,
|
|
172
|
+
connections: this.#connections,
|
|
173
|
+
messages: this.#messages,
|
|
174
|
+
bytes: this.#bytes,
|
|
175
|
+
last_at_ms: this.#lastAtMs,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
105
178
|
/** Replace captured secret values with stable aliases before persistence. */
|
|
106
179
|
redact(value) {
|
|
107
180
|
let redacted = value;
|
|
@@ -113,6 +186,14 @@ export class ReplayMachineRuntime {
|
|
|
113
186
|
redacted = redacted.replaceAll(secret, `<secret:${variable}>`);
|
|
114
187
|
return redacted;
|
|
115
188
|
}
|
|
189
|
+
/** Replace a field containing captured secrets with one stable alias only. */
|
|
190
|
+
redactField(value) {
|
|
191
|
+
const alias = [...this.#sensitiveValues]
|
|
192
|
+
.filter(([, values]) => [...values].some((secret) => value.includes(secret)))
|
|
193
|
+
.map(([variable]) => variable)
|
|
194
|
+
.sort()[0];
|
|
195
|
+
return alias === undefined ? value : `<secret:${alias}>`;
|
|
196
|
+
}
|
|
116
197
|
/** Evaluate and, when admitted, commit one protocol event. */
|
|
117
198
|
dispatch(event) {
|
|
118
199
|
const normalizedEvent = {
|
|
@@ -172,16 +253,13 @@ export class ReplayMachineRuntime {
|
|
|
172
253
|
this.#sensitiveValues.set(capture.variable, values);
|
|
173
254
|
}
|
|
174
255
|
}
|
|
175
|
-
const record = {
|
|
256
|
+
const record = createTransitionRecord({
|
|
176
257
|
sequence: this.#timeline.length,
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
.map(({ variable }) => variable)
|
|
183
|
-
.sort(),
|
|
184
|
-
};
|
|
258
|
+
atMs: normalizedEvent.recorded_at_ms ?? normalizedEvent.at_ms,
|
|
259
|
+
stateBefore: this.#state,
|
|
260
|
+
transition,
|
|
261
|
+
captures,
|
|
262
|
+
});
|
|
185
263
|
this.#uses.set(transition.id, used + 1);
|
|
186
264
|
this.#stateVisits.set(transition.to, nextVisits);
|
|
187
265
|
this.#state = transition.to;
|
|
@@ -207,6 +285,7 @@ export class ReplayMachineRuntime {
|
|
|
207
285
|
this.#messages = messages;
|
|
208
286
|
this.#bytes = bytes;
|
|
209
287
|
this.#lastAtMs = event.at_ms;
|
|
288
|
+
this.#events += 1;
|
|
210
289
|
return true;
|
|
211
290
|
}
|
|
212
291
|
#refusal(outcome) {
|
|
@@ -140,7 +140,7 @@ const EXPECTED = {
|
|
|
140
140
|
"REA function comparison",
|
|
141
141
|
],
|
|
142
142
|
compare_process_captures: [
|
|
143
|
-
"rea.process-comparison/v3",
|
|
143
|
+
["rea.process-comparison/v3", "rea.process-comparison/v4"],
|
|
144
144
|
"rea-process",
|
|
145
145
|
"REA deterministic process harness",
|
|
146
146
|
],
|
|
@@ -302,7 +302,9 @@ const parseRuntime = (input) => {
|
|
|
302
302
|
const assertComparisonIdentity = (evidence) => {
|
|
303
303
|
const expected = EXPECTED[evidence.operation];
|
|
304
304
|
if (expected === undefined ||
|
|
305
|
-
|
|
305
|
+
!(Array.isArray(expected[0])
|
|
306
|
+
? expected[0].includes(evidence.predicate_type)
|
|
307
|
+
: evidence.predicate_type === expected[0]) ||
|
|
306
308
|
evidence.provider.id !== expected[1] ||
|
|
307
309
|
evidence.provider.name !== expected[2] ||
|
|
308
310
|
evidence.provider.version !==
|
|
@@ -26,6 +26,12 @@ const toolResultFailed = (item) => {
|
|
|
26
26
|
const structured = record(result?.structured_content ?? result?.structuredContent);
|
|
27
27
|
return result?.isError === true || structured?.error !== undefined;
|
|
28
28
|
};
|
|
29
|
+
const toolResultErrorCode = (item) => {
|
|
30
|
+
const result = record(item.result ?? item.output);
|
|
31
|
+
const structured = record(result?.structured_content ?? result?.structuredContent);
|
|
32
|
+
const error = record(structured?.error ?? result?.error ?? item.error);
|
|
33
|
+
return textValue(error?.code) ?? null;
|
|
34
|
+
};
|
|
29
35
|
const callFromItem = (value) => {
|
|
30
36
|
const item = record(value);
|
|
31
37
|
if (item?.type !== "mcp_tool_call")
|
|
@@ -40,6 +46,7 @@ const callFromItem = (value) => {
|
|
|
40
46
|
arguments: item.arguments ?? item.input ?? {},
|
|
41
47
|
evidenceIds: evidenceIdsFrom(item.result ?? item.output),
|
|
42
48
|
error: toolResultFailed(item),
|
|
49
|
+
errorCode: toolResultErrorCode(item),
|
|
43
50
|
};
|
|
44
51
|
};
|
|
45
52
|
const agentTextFromItem = (value) => {
|
|
@@ -102,6 +109,8 @@ export const evaluateCodexEvents = (events, expectedFirstTool, options = {}) =>
|
|
|
102
109
|
signatures.set(signature, (signatures.get(signature) ?? 0) + 1);
|
|
103
110
|
}
|
|
104
111
|
const repeatedCallCount = [...signatures.values()].reduce((total, count) => total + Math.max(0, count - 1), 0);
|
|
112
|
+
const inputValidationFailureCount = reaCalls.filter(({ errorCode }) => errorCode === "invalid_request").length;
|
|
113
|
+
const requiredToolSubsequenceMet = containsOrderedSubsequence(reaCalls.filter(({ error }) => !error).map(({ tool }) => tool), options.requiredToolSubsequence ?? []);
|
|
105
114
|
const finalMessage = state.finalMessages.at(-1) ?? "";
|
|
106
115
|
const evidenceIds = [
|
|
107
116
|
...new Set(reaCalls.flatMap(({ evidenceIds: ids }) => ids)),
|
|
@@ -116,6 +125,8 @@ export const evaluateCodexEvents = (events, expectedFirstTool, options = {}) =>
|
|
|
116
125
|
firstTool: reaCalls[0]?.tool ?? null,
|
|
117
126
|
reaCalls,
|
|
118
127
|
repeatedCallCount,
|
|
128
|
+
inputValidationFailureCount,
|
|
129
|
+
requiredToolSubsequenceMet,
|
|
119
130
|
inputTokens: state.inputTokens,
|
|
120
131
|
cachedInputTokens: state.cachedInputTokens,
|
|
121
132
|
outputTokens: state.outputTokens,
|
|
@@ -126,7 +137,20 @@ export const evaluateCodexEvents = (events, expectedFirstTool, options = {}) =>
|
|
|
126
137
|
completionQuality: finalMessage.trim().length >= 80 &&
|
|
127
138
|
authorityHonesty &&
|
|
128
139
|
contentCriteriaMet &&
|
|
140
|
+
requiredToolSubsequenceMet &&
|
|
141
|
+
(options.forbidInputValidationFailures !== true ||
|
|
142
|
+
inputValidationFailureCount === 0) &&
|
|
129
143
|
(options.requireEvidence !== true || finalCitesEvidence),
|
|
130
144
|
authorityHonesty,
|
|
131
145
|
};
|
|
132
146
|
};
|
|
147
|
+
const containsOrderedSubsequence = (values, required) => {
|
|
148
|
+
let index = 0;
|
|
149
|
+
for (const value of values) {
|
|
150
|
+
if (value === required[index])
|
|
151
|
+
index += 1;
|
|
152
|
+
if (index === required.length)
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
return required.length === 0;
|
|
156
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** Generated from package.json and package-lock.json; do not edit. */
|
|
2
2
|
export const PACKAGE_METADATA = {
|
|
3
3
|
name: "rea-agents",
|
|
4
|
-
version: "2.
|
|
4
|
+
version: "2.3.0", // x-release-please-version
|
|
5
5
|
serverSdkVersion: "2.0.0-beta.4",
|
|
6
6
|
clientSdkVersion: "2.0.0-beta.4",
|
|
7
7
|
coreSdkVersion: "2.0.0-beta.4",
|
|
@@ -6,6 +6,7 @@ import { silentLogger } from "../logger.js";
|
|
|
6
6
|
import { PendingOperations } from "../process/PendingOperations.js";
|
|
7
7
|
import { PrivateRuntimeRoot } from "../process/PrivateRuntimeRoot.js";
|
|
8
8
|
import { ProviderStartupDeadline } from "../process/ProviderDeadline.js";
|
|
9
|
+
import { ProviderRunLineage } from "../process/ProviderRunLineage.js";
|
|
9
10
|
import { ProviderProcessSupervisor, } from "../process/ProviderProcess.js";
|
|
10
11
|
import { GHIDRA_MAX_LINE_BYTES, GHIDRA_MAX_QUEUED_REQUESTS, GHIDRA_REQUEST_TIMEOUT_MS, GHIDRA_STARTUP_TIMEOUT_MS, } from "./GhidraDefaults.js";
|
|
11
12
|
import { createGhidraDiagnostics } from "./GhidraDiagnostics.js";
|
|
@@ -33,6 +34,7 @@ export class GhidraClient {
|
|
|
33
34
|
#snapshotPath;
|
|
34
35
|
#token;
|
|
35
36
|
#runId;
|
|
37
|
+
#lineage = new ProviderRunLineage();
|
|
36
38
|
#nextId = 1;
|
|
37
39
|
#closing = false;
|
|
38
40
|
#processSnapshot;
|
|
@@ -145,6 +147,10 @@ export class GhidraClient {
|
|
|
145
147
|
diagnostics() {
|
|
146
148
|
return structuredClone(this.#diagnostics());
|
|
147
149
|
}
|
|
150
|
+
/** Latest token-verified launcher lineage for the active run. */
|
|
151
|
+
runtimeLineage() {
|
|
152
|
+
return this.#lineage.snapshot();
|
|
153
|
+
}
|
|
148
154
|
async #start(signal) {
|
|
149
155
|
if (signal.aborted)
|
|
150
156
|
return err(this.#failure("cancelled", "Ghidra startup was cancelled"));
|
|
@@ -188,7 +194,8 @@ export class GhidraClient {
|
|
|
188
194
|
if (deadline.signal.aborted)
|
|
189
195
|
return this.#startupInterrupted(deadline);
|
|
190
196
|
this.#token = randomBytes(32).toString("hex");
|
|
191
|
-
this.#
|
|
197
|
+
this.#lineage.reset();
|
|
198
|
+
this.#runId = this.#options.runId ?? randomUUID();
|
|
192
199
|
const launched = await this.#options.launcher
|
|
193
200
|
.launch({
|
|
194
201
|
runtimeRoot: this.#runtimeRoot.path,
|
|
@@ -221,7 +228,7 @@ export class GhidraClient {
|
|
|
221
228
|
await this.#cleanup();
|
|
222
229
|
return err(failure);
|
|
223
230
|
}
|
|
224
|
-
|
|
231
|
+
const completed = await completeGhidraStartupHandshake({
|
|
225
232
|
deadline,
|
|
226
233
|
request: (method, params, requestOptions) => this.#wire.request(method, params, requestOptions),
|
|
227
234
|
parseSessionInfo: (value) => this.#parseSessionInfo(value),
|
|
@@ -229,6 +236,9 @@ export class GhidraClient {
|
|
|
229
236
|
failure: this.#failure,
|
|
230
237
|
startupTimeoutMs: this.#options.startupTimeoutMs,
|
|
231
238
|
});
|
|
239
|
+
if (completed.ok)
|
|
240
|
+
await this.#lineage.observe(this.#launch);
|
|
241
|
+
return completed;
|
|
232
242
|
}
|
|
233
243
|
async #connect(endpoint, deadline) {
|
|
234
244
|
const connected = await connectGhidraSocket({
|
|
@@ -373,6 +383,7 @@ export class GhidraClient {
|
|
|
373
383
|
transport: this.#options.transport,
|
|
374
384
|
providerVersion: this.#options.providerVersion,
|
|
375
385
|
profileDigest: this.#options.profileDigest,
|
|
386
|
+
...(this.#runId === undefined ? {} : { runId: this.#runId }),
|
|
376
387
|
...(this.#runtimeRoot === undefined
|
|
377
388
|
? {}
|
|
378
389
|
: { runtimeRoot: this.#runtimeRoot.path }),
|
|
@@ -14,6 +14,7 @@ export const createGhidraDiagnostics = (options) => {
|
|
|
14
14
|
transport: options.transport,
|
|
15
15
|
provider_version: options.providerVersion,
|
|
16
16
|
profile_digest: options.profileDigest,
|
|
17
|
+
...(options.runId === undefined ? {} : { run_id: options.runId }),
|
|
17
18
|
...(options.runtimeRoot === undefined
|
|
18
19
|
? {}
|
|
19
20
|
: {
|
|
@@ -91,9 +91,7 @@ export class GhidraHeadlessLauncher {
|
|
|
91
91
|
}
|
|
92
92
|
const spawned = started;
|
|
93
93
|
return ok({
|
|
94
|
-
|
|
95
|
-
ownsProcessLifetime: true,
|
|
96
|
-
cleanup: () => cleanupStartedProcess(spawned, platform),
|
|
94
|
+
...ownedGhidraProcess(spawned, platform),
|
|
97
95
|
projectRoot: paths.projectRoot,
|
|
98
96
|
ghidraLogPath: paths.ghidraLogPath,
|
|
99
97
|
scriptLogPath: paths.scriptLogPath,
|
|
@@ -108,6 +106,12 @@ export class GhidraHeadlessLauncher {
|
|
|
108
106
|
}
|
|
109
107
|
}
|
|
110
108
|
}
|
|
109
|
+
const ownedGhidraProcess = (spawned, platform) => ({
|
|
110
|
+
process: spawned.process,
|
|
111
|
+
ownsProcessLifetime: true,
|
|
112
|
+
ownership: spawned.ownership,
|
|
113
|
+
cleanup: () => cleanupStartedProcess(spawned, platform),
|
|
114
|
+
});
|
|
111
115
|
/** Build a direct POSIX launch or a conservatively quoted Windows batch call. */
|
|
112
116
|
export const ghidraHeadlessCommand = (options) => {
|
|
113
117
|
if (options.platform !== "win32")
|
|
@@ -95,7 +95,7 @@ export class GhidraProvider {
|
|
|
95
95
|
resolveAnalysisProfile(target, options) {
|
|
96
96
|
return resolveGhidraAnalysisProfile(target, GHIDRA_PROVIDER_IDENTITY, this.#inspectInstallation(), options?.signal);
|
|
97
97
|
}
|
|
98
|
-
createClient(target, profile) {
|
|
98
|
+
createClient(target, profile, context) {
|
|
99
99
|
const installation = this.#inspectInstallation();
|
|
100
100
|
const prerequisites = ghidraClientPrerequisites(target, profile, installation);
|
|
101
101
|
if (!prerequisites.ok)
|
|
@@ -118,6 +118,7 @@ export class GhidraProvider {
|
|
|
118
118
|
: "unix-socket",
|
|
119
119
|
providerVersion: prerequisites.value.providerVersion,
|
|
120
120
|
profileDigest: committedProfile.digest,
|
|
121
|
+
...(context === undefined ? {} : { runId: context.runId }),
|
|
121
122
|
logger: this.logger.child({ layer: "ghidra-bridge" }),
|
|
122
123
|
});
|
|
123
124
|
return {
|
|
@@ -160,6 +161,12 @@ export class GhidraProvider {
|
|
|
160
161
|
limitations: [...limitationsFor(operation), ...providerLimitations],
|
|
161
162
|
}));
|
|
162
163
|
},
|
|
164
|
+
runtimeLineageSnapshots: () => {
|
|
165
|
+
const observation = client.runtimeLineage?.() ?? null;
|
|
166
|
+
return observation === null
|
|
167
|
+
? []
|
|
168
|
+
: [{ provider: committedProfile.provider, observation }];
|
|
169
|
+
},
|
|
163
170
|
close: () => client.close(),
|
|
164
171
|
};
|
|
165
172
|
}
|