rea-agents 0.2.1 → 0.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 +124 -20
- package/bridge/hopper_bridge.py +141 -13
- package/dist/application/AnalysisProvider.js +12 -1
- package/dist/application/ArtifactExtraction.js +166 -0
- package/dist/application/ArtifactGraphConstruction.js +257 -0
- package/dist/application/ArtifactInventory.js +253 -0
- package/dist/application/BinarySession.js +164 -14
- package/dist/application/CompositeProvider.js +73 -0
- package/dist/application/DirectAnalysis.js +27 -3
- package/dist/application/Doctor.js +35 -6
- package/dist/application/EnhancedTools.js +8 -7
- package/dist/application/EvidenceBundleCommands.js +29 -0
- package/dist/application/EvidenceBundleFiles.js +127 -0
- package/dist/application/EvidenceLedger.js +225 -18
- package/dist/application/FilesystemSnapshot.js +124 -0
- package/dist/application/LinuxHopper.js +186 -0
- package/dist/application/LoopbackReplay.js +195 -37
- package/dist/application/ProcessHarness.js +200 -191
- package/dist/application/ProcessNormalization.js +44 -0
- package/dist/application/ProcessOwnership.js +84 -0
- package/dist/application/ProcessSampling.js +284 -0
- package/dist/application/RealHopperAssertions.js +105 -0
- package/dist/application/ReferenceSourceImport.js +182 -0
- package/dist/application/ReferenceSourceImportEntries.js +122 -0
- package/dist/application/ReferenceSourceImportPolicy.js +73 -0
- package/dist/application/ReferenceSourceImportTypes.js +18 -0
- package/dist/application/ReferenceSourceVcsAdapter.js +34 -0
- package/dist/application/Setup.js +192 -41
- package/dist/application/Uninstall.js +130 -0
- package/dist/application/runtime.js +8 -1
- package/dist/artifacts/ArtifactPaths.js +51 -0
- package/dist/artifacts/ArtifactProvider.js +130 -0
- package/dist/artifacts/ArtifactReader.js +9 -0
- package/dist/artifacts/AsarArtifactReader.js +62 -0
- package/dist/artifacts/DirectoryArtifactReader.js +107 -0
- package/dist/artifacts/MachOSliceArtifactReader.js +66 -0
- package/dist/artifacts/SafeOutputTree.js +199 -0
- package/dist/artifacts/StreamBytes.js +10 -0
- package/dist/artifacts/ZipArtifactReader.js +109 -0
- package/dist/cli.js +153 -6
- package/dist/config.js +35 -1
- package/dist/contracts/artifactComparisonExample.js +95 -0
- package/dist/contracts/artifactToolContracts.js +84 -0
- package/dist/contracts/enhancedInputs.js +4 -0
- package/dist/contracts/functionComparisonExample.js +57 -0
- package/dist/contracts/investigationExamples.js +118 -0
- package/dist/contracts/nativeToolContracts.js +52 -0
- package/dist/contracts/processCaptureExample.js +25 -0
- package/dist/contracts/toolContractExamples.js +69 -0
- package/dist/contracts/toolContracts.js +99 -36
- package/dist/contracts/toolOutputSchemas.js +154 -82
- package/dist/contracts/unknownContractExamples.js +33 -0
- package/dist/domain/artifactComparison.js +273 -0
- package/dist/domain/artifactGraph.js +194 -0
- package/dist/domain/artifactInventoryEvidence.js +150 -0
- package/dist/domain/binaryTarget.js +55 -0
- package/dist/domain/bundleComparison.js +266 -0
- package/dist/domain/callPath.js +346 -0
- package/dist/domain/changedBehavior.js +294 -0
- package/dist/domain/errors.js +192 -4
- package/dist/domain/evidence.js +41 -10
- package/dist/domain/evidenceBundle.js +187 -6
- package/dist/domain/functionComparison.js +201 -0
- package/dist/domain/functionComparisonNormalization.js +112 -0
- package/dist/domain/functionComparisonResults.js +54 -0
- package/dist/domain/functionComparisonSchemas.js +82 -0
- package/dist/domain/functionDossierEvidence.js +171 -0
- package/dist/domain/hopperValues.js +14 -7
- package/dist/domain/nativeInspection.js +142 -0
- package/dist/domain/processCapture.js +152 -54
- package/dist/domain/processComparison.js +106 -0
- package/dist/domain/reconstructionUnknowns.js +90 -0
- package/dist/domain/reconstructionVerification.js +285 -0
- package/dist/domain/reconstructionVerificationSchemas.js +126 -0
- package/dist/domain/referenceSourceClassification.js +496 -0
- package/dist/domain/referenceSourceGraph.js +376 -0
- package/dist/domain/referenceSourceImportParsing.js +235 -0
- package/dist/domain/residualUnknown.js +239 -0
- package/dist/domain/staticRuntimeCorrelation.js +375 -0
- package/dist/hopper/BridgeLauncher.js +40 -3
- package/dist/hopper/HopperClient.js +14 -5
- package/dist/hopper/HopperProvider.js +57 -22
- package/dist/identity.js +1 -0
- package/dist/main.js +5 -1
- package/dist/native/CommandRunner.js +156 -0
- package/dist/native/NativeMacOSProvider.js +306 -0
- package/dist/native/NativeMachoInspection.js +135 -0
- package/dist/native/parsers/codesign.js +55 -0
- package/dist/native/parsers/demangle.js +26 -0
- package/dist/native/parsers/dyldInfo.js +25 -0
- package/dist/native/parsers/lipo.js +67 -0
- package/dist/native/parsers/otool.js +193 -0
- package/dist/native/parsers/plist.js +23 -0
- package/dist/reference/ReferenceSourceReader.js +73 -0
- package/dist/reference/ReferenceSourceReaderEntries.js +206 -0
- package/dist/reference/ReferenceSourceReaderErrors.js +19 -0
- package/dist/reference/ReferenceSourceReaderFile.js +119 -0
- package/dist/reference/ReferenceSourceReaderPaths.js +23 -0
- package/dist/reference/ReferenceSourceReaderTypes.js +2 -0
- package/dist/reference/ReferenceSourceReaderValidate.js +71 -0
- package/dist/server/createServer.js +31 -5
- package/dist/server/recordDerivedEvidence.js +10 -0
- package/dist/server/registerArtifactComparisonTool.js +62 -0
- package/dist/server/registerArtifactTools.js +6 -0
- package/dist/server/registerBundleComparisonTool.js +47 -0
- package/dist/server/registerEnhancedTools.js +64 -12
- package/dist/server/registerEvidenceTools.js +36 -0
- package/dist/server/registerFunctionComparisonTool.js +68 -0
- package/dist/server/registerInvestigationTools.js +224 -0
- package/dist/server/registerNativeTools.js +6 -0
- package/dist/server/registerOfficialTools.js +47 -14
- package/dist/server/registerProcessComparisonTool.js +106 -0
- package/dist/server/registerSessionTools.js +179 -70
- package/dist/server/sessionEvidence.js +28 -0
- package/dist/server/sessionToolPolicies.js +64 -0
- package/dist/server/toolRegistrationOptions.js +7 -0
- package/dist/server/toolResult.js +8 -5
- package/install.sh +198 -0
- package/package.json +19 -2
- package/scripts/rea.mjs +5 -1
- package/skills/rea-analysis/SKILL.md +77 -2
- /package/dist/{application/HopperToolPort.js → domain/referenceSourcePolicy.js} +0 -0
|
@@ -2,24 +2,57 @@ import { z } from "zod";
|
|
|
2
2
|
import { SESSION_TOOL_CONTRACTS } from "../contracts/toolContracts.js";
|
|
3
3
|
import { toCallToolResult } from "./toolResult.js";
|
|
4
4
|
import { logToolExecution } from "./toolLogging.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { compareProcessCaptures, processCaptureSchema, processScenarioSchema, } from "../domain/processCapture.js";
|
|
5
|
+
import { ok } from "../domain/result.js";
|
|
6
|
+
import { processScenarioSchema } from "../domain/processCapture.js";
|
|
8
7
|
import { captureProcessScenario } from "../application/ProcessHarness.js";
|
|
9
8
|
import { createEvidence } from "../domain/evidence.js";
|
|
10
9
|
import { jsonValueSchema } from "../domain/jsonValue.js";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
10
|
+
import { recordUnknownInputSchema, updateUnknownInputSchema, } from "../domain/residualUnknown.js";
|
|
11
|
+
import { readEvidenceBundle, writeEvidenceBundle, } from "../application/EvidenceBundleFiles.js";
|
|
12
|
+
import { UnknownRegistryError } from "../domain/errors.js";
|
|
13
|
+
import { DENY_EVIDENCE_FILE_POLICY, DENY_PROCESS_POLICY, PROCESS_PROVIDER, } from "./sessionToolPolicies.js";
|
|
14
|
+
import { registerProcessComparisonTool } from "./registerProcessComparisonTool.js";
|
|
15
|
+
import { registerArtifactComparisonTool } from "./registerArtifactComparisonTool.js";
|
|
16
|
+
import { registerFunctionComparisonTool } from "./registerFunctionComparisonTool.js";
|
|
17
|
+
import { registerBundleComparisonTool } from "./registerBundleComparisonTool.js";
|
|
18
|
+
import { registerInvestigationTools } from "./registerInvestigationTools.js";
|
|
19
|
+
const recordProcessResidualUnknowns = (session, scenario, evidence, residuals) => {
|
|
20
|
+
if (scenario.unknown_registry_approved !== true)
|
|
21
|
+
return ok(null);
|
|
22
|
+
for (const residual of residuals) {
|
|
23
|
+
const unknown = session.recordUnknown({
|
|
24
|
+
approved: true,
|
|
25
|
+
question: `${residual.scope}: ${residual.reason}`,
|
|
26
|
+
severity: "medium",
|
|
27
|
+
domain: `process-${residual.scope}`,
|
|
28
|
+
supporting_evidence_ids: [evidence.evidence_id],
|
|
29
|
+
contradicting_evidence_ids: [],
|
|
30
|
+
required_authority: "controlled-replay",
|
|
31
|
+
required_confidence: "observed",
|
|
32
|
+
required_environment: evidence.environment,
|
|
33
|
+
recommended_probes: [
|
|
34
|
+
{
|
|
35
|
+
operation: "capture_process_scenario",
|
|
36
|
+
rationale: "Repeat with a scenario that observes the missing behavior.",
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
relationships: [],
|
|
40
|
+
});
|
|
41
|
+
if (!unknown.ok &&
|
|
42
|
+
!(unknown.error instanceof UnknownRegistryError &&
|
|
43
|
+
unknown.error.reason === "already-exists"))
|
|
44
|
+
return unknown;
|
|
45
|
+
}
|
|
46
|
+
return ok(null);
|
|
21
47
|
};
|
|
22
|
-
const
|
|
48
|
+
const processEvidenceParameters = (scenario) => ({
|
|
49
|
+
executable_name: scenario.executable.split("/").at(-1) ?? scenario.executable,
|
|
50
|
+
argument_count: scenario.arguments.length,
|
|
51
|
+
event_count: scenario.events.length,
|
|
52
|
+
filesystem_root_count: scenario.filesystem_roots.length,
|
|
53
|
+
normalization: scenario.normalization,
|
|
54
|
+
});
|
|
55
|
+
const registerProcessTools = ({ server, session, logger, processPolicy, captureContract, }) => {
|
|
23
56
|
server.registerTool(captureContract.name, {
|
|
24
57
|
description: captureContract.description,
|
|
25
58
|
inputSchema: captureContract.inputSchema,
|
|
@@ -31,14 +64,9 @@ const registerProcessTools = ({ server, session, logger, processPolicy, captureC
|
|
|
31
64
|
if (!captured.ok)
|
|
32
65
|
return toCallToolResult(captured, captureContract);
|
|
33
66
|
const evidence = createEvidence(undefined, PROCESS_PROVIDER, {
|
|
34
|
-
predicateType: "rea.process-capture/
|
|
67
|
+
predicateType: "rea.process-capture/v2",
|
|
35
68
|
operation: captureContract.name,
|
|
36
|
-
parameters:
|
|
37
|
-
executable_name: scenario.executable.split("/").at(-1) ?? scenario.executable,
|
|
38
|
-
argument_count: scenario.arguments.length,
|
|
39
|
-
event_count: scenario.events.length,
|
|
40
|
-
filesystem_root_count: scenario.filesystem_roots.length,
|
|
41
|
-
},
|
|
69
|
+
parameters: processEvidenceParameters(scenario),
|
|
42
70
|
result: jsonValueSchema.parse(captured.value),
|
|
43
71
|
confidence: "observed",
|
|
44
72
|
authority: "controlled-replay",
|
|
@@ -50,69 +78,115 @@ const registerProcessTools = ({ server, session, logger, processPolicy, captureC
|
|
|
50
78
|
},
|
|
51
79
|
limitations: captured.value.limitations,
|
|
52
80
|
});
|
|
53
|
-
session.recordEvidence(evidence);
|
|
81
|
+
const recorded = session.recordEvidence(evidence);
|
|
82
|
+
if (!recorded.ok)
|
|
83
|
+
return toCallToolResult(recorded, captureContract);
|
|
84
|
+
const unknowns = recordProcessResidualUnknowns(session, scenario, evidence, captured.value.residual_unknowns);
|
|
85
|
+
if (!unknowns.ok)
|
|
86
|
+
return toCallToolResult(unknowns, captureContract);
|
|
54
87
|
return toCallToolResult(ok(evidence), captureContract);
|
|
55
88
|
});
|
|
56
|
-
server.registerTool(compareContract.name, {
|
|
57
|
-
description: compareContract.description,
|
|
58
|
-
inputSchema: compareContract.inputSchema,
|
|
59
|
-
outputSchema: compareContract.outputSchema,
|
|
60
|
-
annotations: compareContract.annotations,
|
|
61
|
-
}, (input) => {
|
|
62
|
-
const parsed = z
|
|
63
|
-
.object({
|
|
64
|
-
left_evidence_id: z.string(),
|
|
65
|
-
left: processCaptureSchema,
|
|
66
|
-
right_evidence_id: z.string(),
|
|
67
|
-
right: processCaptureSchema,
|
|
68
|
-
})
|
|
69
|
-
.parse(input);
|
|
70
|
-
const comparison = compareProcessCaptures(parsed.left, parsed.right);
|
|
71
|
-
const evidence = createEvidence(undefined, PROCESS_PROVIDER, {
|
|
72
|
-
predicateType: "rea.process-comparison/v1",
|
|
73
|
-
operation: compareContract.name,
|
|
74
|
-
parameters: {},
|
|
75
|
-
result: jsonValueSchema.parse(comparison),
|
|
76
|
-
confidence: "derived",
|
|
77
|
-
authority: "analyst-inference",
|
|
78
|
-
limitations: comparison.limitations,
|
|
79
|
-
evidenceLinks: [parsed.left_evidence_id, parsed.right_evidence_id],
|
|
80
|
-
});
|
|
81
|
-
session.recordEvidence(evidence);
|
|
82
|
-
return toCallToolResult(ok(evidence), compareContract);
|
|
83
|
-
});
|
|
84
89
|
};
|
|
85
|
-
const registerEvidenceTools = ({ server, session, exportContract, importContract, }) => {
|
|
90
|
+
const registerEvidenceTools = ({ server, session, exportContract, importContract, filePolicy, }) => {
|
|
86
91
|
server.registerTool(exportContract.name, {
|
|
87
92
|
description: exportContract.description,
|
|
88
93
|
inputSchema: exportContract.inputSchema,
|
|
89
94
|
outputSchema: exportContract.outputSchema,
|
|
90
95
|
annotations: exportContract.annotations,
|
|
91
|
-
}, () =>
|
|
96
|
+
}, async (input) => {
|
|
97
|
+
const parsed = z
|
|
98
|
+
.object({
|
|
99
|
+
path: z.string().min(1).optional(),
|
|
100
|
+
overwrite: z.boolean().default(false),
|
|
101
|
+
})
|
|
102
|
+
.parse(input);
|
|
103
|
+
const bundle = session.exportEvidenceBundle();
|
|
104
|
+
if (parsed.path === undefined)
|
|
105
|
+
return toCallToolResult(ok(bundle), exportContract);
|
|
106
|
+
const written = await writeEvidenceBundle(bundle, parsed.path, parsed.overwrite, filePolicy);
|
|
107
|
+
return written.ok
|
|
108
|
+
? toCallToolResult(ok({
|
|
109
|
+
path: written.value.path,
|
|
110
|
+
bytes: written.value.bytes,
|
|
111
|
+
records: bundle.records.length,
|
|
112
|
+
}), exportContract)
|
|
113
|
+
: toCallToolResult(written, exportContract);
|
|
114
|
+
});
|
|
92
115
|
server.registerTool(importContract.name, {
|
|
93
116
|
description: importContract.description,
|
|
94
117
|
inputSchema: importContract.inputSchema,
|
|
95
118
|
outputSchema: importContract.outputSchema,
|
|
96
119
|
annotations: importContract.annotations,
|
|
97
|
-
}, (input) => {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
return toCallToolResult(
|
|
102
|
-
|
|
120
|
+
}, async (input) => {
|
|
121
|
+
const path = z.object({ path: z.string().min(1) }).parse(input).path;
|
|
122
|
+
const loaded = await readEvidenceBundle(path, filePolicy);
|
|
123
|
+
if (!loaded.ok)
|
|
124
|
+
return toCallToolResult(loaded, importContract);
|
|
125
|
+
const imported = session.importEvidenceBundle(loaded.value);
|
|
126
|
+
return imported.ok
|
|
127
|
+
? toCallToolResult(ok({
|
|
128
|
+
imported: imported.value,
|
|
103
129
|
total: session.exportEvidenceBundle().records.length,
|
|
104
|
-
}), importContract)
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
130
|
+
}), importContract)
|
|
131
|
+
: toCallToolResult(imported, importContract);
|
|
132
|
+
});
|
|
133
|
+
};
|
|
134
|
+
const registerUnknownTools = ({ server, session, contracts: [listContract, recordContract, updateContract, verifyContract], }) => {
|
|
135
|
+
server.registerTool(listContract.name, {
|
|
136
|
+
description: listContract.description,
|
|
137
|
+
inputSchema: listContract.inputSchema,
|
|
138
|
+
outputSchema: listContract.outputSchema,
|
|
139
|
+
annotations: listContract.annotations,
|
|
140
|
+
}, (input) => {
|
|
141
|
+
const filters = z
|
|
142
|
+
.object({
|
|
143
|
+
status: z
|
|
144
|
+
.enum([
|
|
145
|
+
"open",
|
|
146
|
+
"investigating",
|
|
147
|
+
"blocked",
|
|
148
|
+
"contradicted",
|
|
149
|
+
"resolved",
|
|
150
|
+
])
|
|
151
|
+
.optional(),
|
|
152
|
+
severity: z.enum(["low", "medium", "high", "critical"]).optional(),
|
|
153
|
+
domain: z.string().trim().min(1).max(100).optional(),
|
|
154
|
+
})
|
|
155
|
+
.parse(input);
|
|
156
|
+
return toCallToolResult(ok(session.listUnknowns({
|
|
157
|
+
...(filters.status === undefined ? {} : { status: filters.status }),
|
|
158
|
+
...(filters.severity === undefined
|
|
159
|
+
? {}
|
|
160
|
+
: { severity: filters.severity }),
|
|
161
|
+
...(filters.domain === undefined ? {} : { domain: filters.domain }),
|
|
162
|
+
})), listContract);
|
|
163
|
+
});
|
|
164
|
+
server.registerTool(recordContract.name, {
|
|
165
|
+
description: recordContract.description,
|
|
166
|
+
inputSchema: recordContract.inputSchema,
|
|
167
|
+
outputSchema: recordContract.outputSchema,
|
|
168
|
+
annotations: recordContract.annotations,
|
|
169
|
+
}, (input) => toCallToolResult(session.recordUnknown(recordUnknownInputSchema.parse(input)), recordContract));
|
|
170
|
+
server.registerTool(updateContract.name, {
|
|
171
|
+
description: updateContract.description,
|
|
172
|
+
inputSchema: updateContract.inputSchema,
|
|
173
|
+
outputSchema: updateContract.outputSchema,
|
|
174
|
+
annotations: updateContract.annotations,
|
|
175
|
+
}, (input) => toCallToolResult(session.updateUnknown(updateUnknownInputSchema.parse(input)), updateContract));
|
|
176
|
+
server.registerTool(verifyContract.name, {
|
|
177
|
+
description: verifyContract.description,
|
|
178
|
+
inputSchema: verifyContract.inputSchema,
|
|
179
|
+
outputSchema: verifyContract.outputSchema,
|
|
180
|
+
annotations: verifyContract.annotations,
|
|
181
|
+
}, (input) => {
|
|
182
|
+
const unknownId = z
|
|
183
|
+
.object({ unknown_id: z.string().regex(/^unk_[a-f0-9]{64}$/u) })
|
|
184
|
+
.parse(input).unknown_id;
|
|
185
|
+
return toCallToolResult(session.verifyUnknownResolution(unknownId), verifyContract);
|
|
111
186
|
});
|
|
112
187
|
};
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const [openContract, closeContract, statusContract, exportContract, importContract, captureContract, compareContract,] = SESSION_TOOL_CONTRACTS;
|
|
188
|
+
const registerLifecycleTools = (server, session, logger, contracts) => {
|
|
189
|
+
const [openContract, closeContract, statusContract] = contracts;
|
|
116
190
|
server.registerTool(openContract.name, {
|
|
117
191
|
description: openContract.description,
|
|
118
192
|
inputSchema: openContract.inputSchema,
|
|
@@ -147,13 +221,48 @@ export const registerSessionTools = (server, session, logger, processPolicy = DE
|
|
|
147
221
|
outputSchema: statusContract.outputSchema,
|
|
148
222
|
annotations: statusContract.annotations,
|
|
149
223
|
}, () => toCallToolResult({ ok: true, value: session.status() }, statusContract));
|
|
150
|
-
|
|
224
|
+
};
|
|
225
|
+
export const registerSessionTools = (server, session, logger, options = {}) => {
|
|
226
|
+
const processPolicy = options.processPolicy ?? DENY_PROCESS_POLICY;
|
|
227
|
+
const evidenceFilePolicy = options.evidenceFilePolicy ?? DENY_EVIDENCE_FILE_POLICY;
|
|
228
|
+
const [openContract, closeContract, statusContract, exportContract, importContract, captureContract, compareContract, compareArtifactsContract, compareFunctionsContract, compareBundlesContract, changedBehaviorContract, callPathContract, staticRuntimeContract, reconstructionContract, listUnknownContract, recordUnknownContract, updateUnknownContract, verifyUnknownContract,] = SESSION_TOOL_CONTRACTS;
|
|
229
|
+
registerLifecycleTools(server, session, logger, [
|
|
230
|
+
openContract,
|
|
231
|
+
closeContract,
|
|
232
|
+
statusContract,
|
|
233
|
+
]);
|
|
234
|
+
registerEvidenceTools({
|
|
235
|
+
server,
|
|
236
|
+
session,
|
|
237
|
+
exportContract,
|
|
238
|
+
importContract,
|
|
239
|
+
filePolicy: evidenceFilePolicy,
|
|
240
|
+
});
|
|
151
241
|
registerProcessTools({
|
|
152
242
|
server,
|
|
153
243
|
session,
|
|
154
244
|
logger,
|
|
155
245
|
processPolicy,
|
|
156
246
|
captureContract,
|
|
157
|
-
|
|
247
|
+
});
|
|
248
|
+
registerProcessComparisonTool(server, session, compareContract);
|
|
249
|
+
registerArtifactComparisonTool(server, session, compareArtifactsContract);
|
|
250
|
+
registerFunctionComparisonTool(server, session, compareFunctionsContract);
|
|
251
|
+
registerBundleComparisonTool(server, session, compareBundlesContract);
|
|
252
|
+
registerInvestigationTools(server, session, [
|
|
253
|
+
changedBehaviorContract,
|
|
254
|
+
callPathContract,
|
|
255
|
+
staticRuntimeContract,
|
|
256
|
+
reconstructionContract,
|
|
257
|
+
]);
|
|
258
|
+
registerUnknownTools({
|
|
259
|
+
server,
|
|
260
|
+
session,
|
|
261
|
+
contracts: [
|
|
262
|
+
listUnknownContract,
|
|
263
|
+
recordUnknownContract,
|
|
264
|
+
updateUnknownContract,
|
|
265
|
+
verifyUnknownContract,
|
|
266
|
+
],
|
|
158
267
|
});
|
|
159
268
|
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import canonicalize from "canonicalize";
|
|
2
|
+
import { EvidenceIntegrityError } from "../domain/errors.js";
|
|
3
|
+
import { parseEvidence } from "../domain/evidence.js";
|
|
4
|
+
import { err, ok } from "../domain/result.js";
|
|
5
|
+
/** Resolve caller-supplied records to their immutable session-owned values. */
|
|
6
|
+
export const resolveSessionEvidence = (session, input) => {
|
|
7
|
+
const supplied = Array.isArray(input) ? input : [input];
|
|
8
|
+
const authoritative = [];
|
|
9
|
+
for (const item of supplied) {
|
|
10
|
+
const parsed = parseEvidence(item);
|
|
11
|
+
const owned = session.evidenceById(parsed.evidence_id);
|
|
12
|
+
if (owned === undefined || canonicalJson(owned) !== canonicalJson(parsed))
|
|
13
|
+
return err(new EvidenceIntegrityError("Comparison input does not match its session-owned Evidence"));
|
|
14
|
+
authoritative.push(owned);
|
|
15
|
+
}
|
|
16
|
+
return ok(authoritative);
|
|
17
|
+
};
|
|
18
|
+
/** Check exact ownership for a single Evidence record. */
|
|
19
|
+
export const isSessionEvidence = (session, input) => {
|
|
20
|
+
const owned = session.evidenceById(input.evidence_id);
|
|
21
|
+
return owned !== undefined && canonicalJson(owned) === canonicalJson(input);
|
|
22
|
+
};
|
|
23
|
+
const canonicalJson = (value) => {
|
|
24
|
+
const encoded = canonicalize(value);
|
|
25
|
+
if (encoded === undefined)
|
|
26
|
+
throw new TypeError("Evidence could not be canonicalized");
|
|
27
|
+
return encoded;
|
|
28
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/** Fail-closed process policy used when operator configuration is absent. */
|
|
2
|
+
export const DENY_PROCESS_POLICY = {
|
|
3
|
+
enabled: false,
|
|
4
|
+
executableRoots: [],
|
|
5
|
+
workingRoots: [],
|
|
6
|
+
allowedEnvironment: [],
|
|
7
|
+
allowExternalNetwork: false,
|
|
8
|
+
};
|
|
9
|
+
/** Fail-closed evidence filesystem policy used without approved roots. */
|
|
10
|
+
export const DENY_EVIDENCE_FILE_POLICY = {
|
|
11
|
+
roots: [],
|
|
12
|
+
maxBytes: 64 * 1024 * 1024,
|
|
13
|
+
maxDepth: 64,
|
|
14
|
+
maxStringLength: 1024 * 1024,
|
|
15
|
+
maxNodes: 1_000_000,
|
|
16
|
+
};
|
|
17
|
+
/** Provider identity for controlled process-capture evidence. */
|
|
18
|
+
export const PROCESS_PROVIDER = {
|
|
19
|
+
id: "rea-process",
|
|
20
|
+
name: "REA deterministic process harness",
|
|
21
|
+
version: "1",
|
|
22
|
+
};
|
|
23
|
+
/** Provider identity for deterministic cross-artifact comparisons. */
|
|
24
|
+
export const ARTIFACT_COMPARISON_PROVIDER = {
|
|
25
|
+
id: "rea-artifact-comparison",
|
|
26
|
+
name: "REA artifact comparison",
|
|
27
|
+
version: "1",
|
|
28
|
+
};
|
|
29
|
+
/** Provider identity for deterministic cross-function comparisons. */
|
|
30
|
+
export const FUNCTION_COMPARISON_PROVIDER = {
|
|
31
|
+
id: "rea-function-comparison",
|
|
32
|
+
name: "REA function comparison",
|
|
33
|
+
version: "1",
|
|
34
|
+
};
|
|
35
|
+
/** Provider identity for deterministic Evidence bundle comparisons. */
|
|
36
|
+
export const BUNDLE_COMPARISON_PROVIDER = {
|
|
37
|
+
id: "rea-bundle-comparison",
|
|
38
|
+
name: "REA Evidence bundle comparison",
|
|
39
|
+
version: "1",
|
|
40
|
+
};
|
|
41
|
+
/** Provider identity for comparison-Evidence behavior aggregation. */
|
|
42
|
+
export const CHANGED_BEHAVIOR_PROVIDER = {
|
|
43
|
+
id: "rea-changed-behavior",
|
|
44
|
+
name: "REA changed behavior investigation",
|
|
45
|
+
version: "1",
|
|
46
|
+
};
|
|
47
|
+
/** Provider identity for Evidence-backed call-path reconstruction. */
|
|
48
|
+
export const CALL_PATH_PROVIDER = {
|
|
49
|
+
id: "rea-call-path",
|
|
50
|
+
name: "REA call path reconstruction",
|
|
51
|
+
version: "1",
|
|
52
|
+
};
|
|
53
|
+
/** Provider identity for explicit static/runtime hypothesis correlation. */
|
|
54
|
+
export const STATIC_RUNTIME_PROVIDER = {
|
|
55
|
+
id: "rea-static-runtime-correlation",
|
|
56
|
+
name: "REA static/runtime correlation",
|
|
57
|
+
version: "1",
|
|
58
|
+
};
|
|
59
|
+
/** Provider identity for finite reconstruction specification verification. */
|
|
60
|
+
export const RECONSTRUCTION_PROVIDER = {
|
|
61
|
+
id: "rea-reconstruction-verifier",
|
|
62
|
+
name: "REA reconstruction verifier",
|
|
63
|
+
version: "1",
|
|
64
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Project the public contract fields accepted by MCP tool registration. */
|
|
2
|
+
export const toolRegistrationOptions = (contract) => ({
|
|
3
|
+
description: contract.description,
|
|
4
|
+
inputSchema: contract.inputSchema,
|
|
5
|
+
outputSchema: contract.outputSchema,
|
|
6
|
+
annotations: contract.annotations,
|
|
7
|
+
});
|
|
@@ -1,19 +1,22 @@
|
|
|
1
|
+
import { projectAnalysisError } from "../domain/errors.js";
|
|
1
2
|
/**
|
|
2
3
|
* Translate an application result into MCP text content.
|
|
3
4
|
* Error tags and safe messages remain visible while underlying causes, process
|
|
4
5
|
* output, and other potentially sensitive details stay private.
|
|
5
6
|
*/
|
|
6
|
-
export const toCallToolResult = (result, contract) => result.ok
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
export const toCallToolResult = (result, contract) => result.ok ? successResult(result.value, contract) : errorResult(result.error);
|
|
8
|
+
const errorResult = (error) => {
|
|
9
|
+
const projected = projectAnalysisError(error);
|
|
10
|
+
return {
|
|
9
11
|
content: [
|
|
10
12
|
{
|
|
11
13
|
type: "text",
|
|
12
|
-
text: `${
|
|
14
|
+
text: `${projected.tag}: ${projected.message}`,
|
|
13
15
|
},
|
|
14
16
|
],
|
|
15
17
|
isError: true,
|
|
16
18
|
};
|
|
19
|
+
};
|
|
17
20
|
const successResult = (value, contract) => {
|
|
18
21
|
const candidate = contract.kind === "session" ? { result: value } : value;
|
|
19
22
|
const parsed = contract.outputSchema.safeParse(candidate);
|
|
@@ -22,7 +25,7 @@ const successResult = (value, contract) => {
|
|
|
22
25
|
content: [
|
|
23
26
|
{
|
|
24
27
|
type: "text",
|
|
25
|
-
text: "
|
|
28
|
+
text: "AnalysisOutputError: Analysis output does not match the tool contract",
|
|
26
29
|
},
|
|
27
30
|
],
|
|
28
31
|
isError: true,
|
package/install.sh
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
PACKAGE="rea-agents"
|
|
5
|
+
REPOSITORY="morluto/rea"
|
|
6
|
+
NODE_FORMULA="node@24"
|
|
7
|
+
NODE_VERSION="24.18.0"
|
|
8
|
+
temporary_directory=""
|
|
9
|
+
node_destination=""
|
|
10
|
+
node_stage=""
|
|
11
|
+
node_backup=""
|
|
12
|
+
node_version=""
|
|
13
|
+
|
|
14
|
+
cleanup() {
|
|
15
|
+
if [[ -n "$temporary_directory" && -d "$temporary_directory" ]]; then
|
|
16
|
+
rm -rf -- "$temporary_directory"
|
|
17
|
+
fi
|
|
18
|
+
if [[ -n "$node_stage" && -e "$node_stage" ]]; then
|
|
19
|
+
rm -rf -- "$node_stage"
|
|
20
|
+
fi
|
|
21
|
+
if [[ -n "$node_backup" && -e "$node_backup" && ! -e "$node_destination" ]]; then
|
|
22
|
+
mv -- "$node_backup" "$node_destination"
|
|
23
|
+
fi
|
|
24
|
+
}
|
|
25
|
+
trap cleanup EXIT HUP INT TERM
|
|
26
|
+
|
|
27
|
+
fail() {
|
|
28
|
+
printf 'REA installation failed: %s\n' "$1" >&2
|
|
29
|
+
exit 1
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
version_at_least() {
|
|
33
|
+
local actual="$1" required_major="$2" required_minor="$3"
|
|
34
|
+
local major minor
|
|
35
|
+
major="${actual%%.*}"
|
|
36
|
+
minor="${actual#*.}"
|
|
37
|
+
minor="${minor%%.*}"
|
|
38
|
+
[[ "$major" =~ ^[0-9]+$ && "$minor" =~ ^[0-9]+$ ]] || return 1
|
|
39
|
+
((major > required_major || (major == required_major && minor >= required_minor)))
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
platform="$(uname -s)"
|
|
43
|
+
if [[ "$platform" == "Darwin" ]]; then
|
|
44
|
+
macos_version="$(sw_vers -productVersion 2>/dev/null)" || fail "could not determine the macOS version."
|
|
45
|
+
macos_major="${macos_version%%.*}"
|
|
46
|
+
[[ "$macos_major" =~ ^[0-9]+$ && "$macos_major" -ge 12 ]] || fail "macOS 12 or newer is required."
|
|
47
|
+
elif [[ "$platform" == "Linux" ]]; then
|
|
48
|
+
[[ "$(uname -m)" == "x86_64" || "$(uname -m)" == "aarch64" ]] || fail "64-bit x86 or ARM Linux is required."
|
|
49
|
+
[[ -r /etc/os-release ]] || fail "could not identify the Linux distribution."
|
|
50
|
+
linux_id="$(sed -n 's/^ID=//p' /etc/os-release | tr -d '"')"
|
|
51
|
+
linux_version="$(sed -n 's/^VERSION_ID=//p' /etc/os-release | tr -d '"')"
|
|
52
|
+
case "$linux_id" in
|
|
53
|
+
ubuntu) [[ "${linux_version%%.*}" -ge 24 ]] || fail "Ubuntu 24.04 or newer is required." ;;
|
|
54
|
+
fedora) [[ "${linux_version%%.*}" -ge 41 ]] || fail "Fedora 41 or newer is required." ;;
|
|
55
|
+
arch) ;;
|
|
56
|
+
*) fail "supported Linux distributions are Ubuntu 24.04+, Fedora 41+, and Arch Linux." ;;
|
|
57
|
+
esac
|
|
58
|
+
else
|
|
59
|
+
fail "REA supports macOS and selected 64-bit Linux distributions."
|
|
60
|
+
fi
|
|
61
|
+
command -v curl >/dev/null 2>&1 || fail "curl is required."
|
|
62
|
+
command -v shasum >/dev/null 2>&1 || command -v sha256sum >/dev/null 2>&1 || fail "shasum or sha256sum is required."
|
|
63
|
+
|
|
64
|
+
if [[ "$platform" == "Darwin" ]] && ! command -v brew >/dev/null 2>&1; then
|
|
65
|
+
printf 'Installing Homebrew prerequisite...\n'
|
|
66
|
+
NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" || fail "Homebrew installation did not complete."
|
|
67
|
+
if [[ -x /opt/homebrew/bin/brew ]]; then
|
|
68
|
+
PATH="/opt/homebrew/bin:$PATH"
|
|
69
|
+
elif [[ -x /usr/local/bin/brew ]]; then
|
|
70
|
+
PATH="/usr/local/bin:$PATH"
|
|
71
|
+
fi
|
|
72
|
+
fi
|
|
73
|
+
if [[ "$platform" == "Darwin" ]]; then
|
|
74
|
+
command -v brew >/dev/null 2>&1 || fail "Homebrew is not available after installation."
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
node_compatible=false
|
|
78
|
+
if command -v node >/dev/null 2>&1; then
|
|
79
|
+
node_version="$(node -p 'process.versions.node' 2>/dev/null || true)"
|
|
80
|
+
fi
|
|
81
|
+
if version_at_least "$node_version" 24 18 && [[ "$node_version" =~ ^24\. ]]; then
|
|
82
|
+
node_compatible=true
|
|
83
|
+
fi
|
|
84
|
+
if [[ "$node_compatible" != true ]]; then
|
|
85
|
+
if [[ "$platform" == "Darwin" ]]; then
|
|
86
|
+
brew install "$NODE_FORMULA" || fail "Node.js 24 installation failed."
|
|
87
|
+
node_prefix="$(brew --prefix "$NODE_FORMULA")" || fail "could not locate Node.js 24."
|
|
88
|
+
PATH="$node_prefix/bin:$PATH"
|
|
89
|
+
else
|
|
90
|
+
machine="$(uname -m)"
|
|
91
|
+
node_arch="$([[ "$machine" == "x86_64" ]] && printf 'x64' || printf 'arm64')"
|
|
92
|
+
node_archive="node-v${NODE_VERSION}-linux-${node_arch}.tar.xz"
|
|
93
|
+
node_base="https://nodejs.org/dist/v${NODE_VERSION}"
|
|
94
|
+
temporary_directory="$(mktemp -d "${TMPDIR:-/tmp}/rea-install.XXXXXX")" || fail "could not create a temporary directory."
|
|
95
|
+
curl -fsSL "$node_base/$node_archive" -o "$temporary_directory/$node_archive" || fail "Node.js download failed."
|
|
96
|
+
curl -fsSL "$node_base/SHASUMS256.txt" -o "$temporary_directory/SHASUMS256.txt" || fail "Node.js checksum download failed."
|
|
97
|
+
expected_checksum="$(awk -v archive="$node_archive" '$2 == archive { print $1 }' "$temporary_directory/SHASUMS256.txt")"
|
|
98
|
+
[[ "$expected_checksum" =~ ^[a-f0-9]{64}$ ]] || fail "Node.js checksum metadata was malformed."
|
|
99
|
+
if command -v shasum >/dev/null 2>&1; then
|
|
100
|
+
actual_checksum="$(shasum -a 256 "$temporary_directory/$node_archive" | awk '{ print $1 }')"
|
|
101
|
+
else
|
|
102
|
+
actual_checksum="$(sha256sum "$temporary_directory/$node_archive" | awk '{ print $1 }')"
|
|
103
|
+
fi
|
|
104
|
+
[[ "$actual_checksum" == "$expected_checksum" ]] || fail "Node.js checksum verification failed."
|
|
105
|
+
node_destination="$HOME/.local/share/rea/node"
|
|
106
|
+
node_stage="$HOME/.local/share/rea/node.new.$$"
|
|
107
|
+
node_backup="$HOME/.local/share/rea/node.backup.$$"
|
|
108
|
+
[[ ! -L "$node_destination" ]] || fail "refusing to replace a symlinked Node.js installation."
|
|
109
|
+
mkdir -p "$HOME/.local/share/rea"
|
|
110
|
+
rm -rf -- "$node_stage" "$node_backup"
|
|
111
|
+
mkdir "$node_stage"
|
|
112
|
+
tar -xJf "$temporary_directory/$node_archive" --strip-components=1 -C "$node_stage" || fail "Node.js extraction failed."
|
|
113
|
+
if [[ -e "$node_destination" ]]; then
|
|
114
|
+
mv -- "$node_destination" "$node_backup"
|
|
115
|
+
fi
|
|
116
|
+
if ! mv -- "$node_stage" "$node_destination"; then
|
|
117
|
+
[[ ! -e "$node_backup" ]] || mv -- "$node_backup" "$node_destination"
|
|
118
|
+
fail "Node.js installation replacement failed."
|
|
119
|
+
fi
|
|
120
|
+
node_stage=""
|
|
121
|
+
rm -rf -- "$node_backup"
|
|
122
|
+
node_backup=""
|
|
123
|
+
PATH="$node_destination/bin:$PATH"
|
|
124
|
+
fi
|
|
125
|
+
fi
|
|
126
|
+
node_version="$(node -p 'process.versions.node' 2>/dev/null || true)"
|
|
127
|
+
if ! command -v node >/dev/null 2>&1 || ! version_at_least "$node_version" 24 18 || [[ ! "$node_version" =~ ^24\. ]]; then
|
|
128
|
+
fail "Node.js 24.18 or newer is not active on PATH."
|
|
129
|
+
fi
|
|
130
|
+
command -v npm >/dev/null 2>&1 || fail "npm is missing from the Node.js installation."
|
|
131
|
+
npm_version="$(npm --version)"
|
|
132
|
+
if [[ ! "$npm_version" =~ ^11\. ]] || ! version_at_least "$npm_version" 11 16; then
|
|
133
|
+
if [[ "$platform" == "Linux" ]]; then
|
|
134
|
+
npm install --global --prefix "$HOME/.local" npm@11 || fail "npm 11 installation failed."
|
|
135
|
+
else
|
|
136
|
+
npm install --global npm@11 || fail "npm 11 installation failed."
|
|
137
|
+
fi
|
|
138
|
+
fi
|
|
139
|
+
npm_version="$(npm --version)"
|
|
140
|
+
if [[ ! "$npm_version" =~ ^11\. ]] || ! version_at_least "$npm_version" 11 16; then
|
|
141
|
+
fail "npm 11.16 or newer is not active on PATH."
|
|
142
|
+
fi
|
|
143
|
+
if [[ -n "$temporary_directory" ]]; then
|
|
144
|
+
rm -rf -- "$temporary_directory"
|
|
145
|
+
temporary_directory=""
|
|
146
|
+
fi
|
|
147
|
+
|
|
148
|
+
if [[ -n "${REA_VERSION:-}" ]]; then
|
|
149
|
+
version="$REA_VERSION"
|
|
150
|
+
[[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]] || fail "REA_VERSION must be an exact semantic version."
|
|
151
|
+
else
|
|
152
|
+
release_json="$(curl -fsSL -H 'Accept: application/vnd.github+json' "https://api.github.com/repos/$REPOSITORY/releases/latest")" || fail "could not resolve the latest GitHub release."
|
|
153
|
+
tag="$(printf '%s' "$release_json" | node -e 'let s="";process.stdin.on("data",c=>s+=c).on("end",()=>{const v=JSON.parse(s).tag_name;if(typeof v!=="string")process.exit(1);process.stdout.write(v)})')" || fail "GitHub returned malformed release metadata."
|
|
154
|
+
[[ "$tag" =~ ^rea-agents-([0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?)$ ]] || fail "latest release tag is not a valid rea-agents release."
|
|
155
|
+
version="${BASH_REMATCH[1]}"
|
|
156
|
+
fi
|
|
157
|
+
|
|
158
|
+
printf 'Installing %s@%s...\n' "$PACKAGE" "$version"
|
|
159
|
+
if [[ "$platform" == "Linux" ]]; then
|
|
160
|
+
mkdir -p "$HOME/.local/bin"
|
|
161
|
+
PATH="$HOME/.local/bin:$PATH"
|
|
162
|
+
npm install --global --prefix "$HOME/.local" "$PACKAGE@$version" || fail "npm package installation failed."
|
|
163
|
+
else
|
|
164
|
+
npm install --global "$PACKAGE@$version" || fail "npm package installation failed."
|
|
165
|
+
fi
|
|
166
|
+
command -v rea >/dev/null 2>&1 || fail "the rea command is not available after installation."
|
|
167
|
+
installed_version="$(rea --version 2>/dev/null | tr -d '[:space:]')" || fail "could not read the installed REA version."
|
|
168
|
+
[[ "$installed_version" == "$version" ]] || fail "installed version $installed_version does not match $version."
|
|
169
|
+
|
|
170
|
+
temporary_directory="$(mktemp -d "${TMPDIR:-/tmp}/rea-install.XXXXXX")" || fail "could not create a temporary directory."
|
|
171
|
+
setup_output="$temporary_directory/setup.json"
|
|
172
|
+
doctor_output="$temporary_directory/doctor.json"
|
|
173
|
+
rea setup --yes --json >"$setup_output" || fail "REA setup command failed."
|
|
174
|
+
rea doctor --json >"$doctor_output" || fail "REA doctor command failed."
|
|
175
|
+
result="$(node - "$setup_output" "$doctor_output" <<'NODE'
|
|
176
|
+
const fs = require("node:fs");
|
|
177
|
+
const [setupPath, doctorPath] = process.argv.slice(2);
|
|
178
|
+
try {
|
|
179
|
+
const setup = JSON.parse(fs.readFileSync(setupPath, "utf8"));
|
|
180
|
+
const doctor = JSON.parse(fs.readFileSync(doctorPath, "utf8"));
|
|
181
|
+
if (!Array.isArray(doctor.checks) || typeof doctor.healthy !== "boolean") process.exit(2);
|
|
182
|
+
if (setup.status !== "ready" && setup.status !== "needs_human") process.exit(2);
|
|
183
|
+
const activation = setup.actions.includes("installed_hopper");
|
|
184
|
+
if (!doctor.healthy && !activation) process.exit(3);
|
|
185
|
+
process.stdout.write(activation ? "activation" : "ready");
|
|
186
|
+
} catch { process.exit(2); }
|
|
187
|
+
NODE
|
|
188
|
+
)" || fail "setup or doctor reported an unhealthy required component or malformed output."
|
|
189
|
+
|
|
190
|
+
if [[ "$result" == "activation" ]]; then
|
|
191
|
+
printf 'REA %s is installed and coding agents are configured.\n' "$version"
|
|
192
|
+
printf 'One user action remains: open Hopper, complete its activation, then run: rea doctor --json\n'
|
|
193
|
+
else
|
|
194
|
+
printf 'REA %s is installed and ready. Run rea doctor --json at any time.\n' "$version"
|
|
195
|
+
fi
|
|
196
|
+
if [[ "$platform" == "Linux" ]]; then
|
|
197
|
+
printf 'Add %s to PATH to run rea from future shells.\n' "$HOME/.local/bin"
|
|
198
|
+
fi
|