rea-agents 0.3.0 → 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.
Files changed (122) hide show
  1. package/README.md +124 -20
  2. package/bridge/hopper_bridge.py +141 -13
  3. package/dist/application/AnalysisProvider.js +12 -1
  4. package/dist/application/ArtifactExtraction.js +166 -0
  5. package/dist/application/ArtifactGraphConstruction.js +257 -0
  6. package/dist/application/ArtifactInventory.js +253 -0
  7. package/dist/application/BinarySession.js +164 -14
  8. package/dist/application/CompositeProvider.js +73 -0
  9. package/dist/application/DirectAnalysis.js +27 -3
  10. package/dist/application/Doctor.js +35 -6
  11. package/dist/application/EnhancedTools.js +8 -7
  12. package/dist/application/EvidenceBundleCommands.js +29 -0
  13. package/dist/application/EvidenceBundleFiles.js +127 -0
  14. package/dist/application/EvidenceLedger.js +225 -18
  15. package/dist/application/FilesystemSnapshot.js +124 -0
  16. package/dist/application/LinuxHopper.js +186 -0
  17. package/dist/application/LoopbackReplay.js +195 -37
  18. package/dist/application/ProcessHarness.js +200 -191
  19. package/dist/application/ProcessNormalization.js +44 -0
  20. package/dist/application/ProcessOwnership.js +84 -0
  21. package/dist/application/ProcessSampling.js +284 -0
  22. package/dist/application/RealHopperAssertions.js +105 -0
  23. package/dist/application/ReferenceSourceImport.js +182 -0
  24. package/dist/application/ReferenceSourceImportEntries.js +122 -0
  25. package/dist/application/ReferenceSourceImportPolicy.js +73 -0
  26. package/dist/application/ReferenceSourceImportTypes.js +18 -0
  27. package/dist/application/ReferenceSourceVcsAdapter.js +34 -0
  28. package/dist/application/Setup.js +192 -41
  29. package/dist/application/Uninstall.js +130 -0
  30. package/dist/application/runtime.js +8 -1
  31. package/dist/artifacts/ArtifactPaths.js +51 -0
  32. package/dist/artifacts/ArtifactProvider.js +130 -0
  33. package/dist/artifacts/ArtifactReader.js +9 -0
  34. package/dist/artifacts/AsarArtifactReader.js +62 -0
  35. package/dist/artifacts/DirectoryArtifactReader.js +107 -0
  36. package/dist/artifacts/MachOSliceArtifactReader.js +66 -0
  37. package/dist/artifacts/SafeOutputTree.js +199 -0
  38. package/dist/artifacts/StreamBytes.js +10 -0
  39. package/dist/artifacts/ZipArtifactReader.js +109 -0
  40. package/dist/cli.js +149 -2
  41. package/dist/config.js +35 -1
  42. package/dist/contracts/artifactComparisonExample.js +95 -0
  43. package/dist/contracts/artifactToolContracts.js +84 -0
  44. package/dist/contracts/enhancedInputs.js +4 -0
  45. package/dist/contracts/functionComparisonExample.js +57 -0
  46. package/dist/contracts/investigationExamples.js +118 -0
  47. package/dist/contracts/nativeToolContracts.js +52 -0
  48. package/dist/contracts/processCaptureExample.js +25 -0
  49. package/dist/contracts/toolContractExamples.js +69 -0
  50. package/dist/contracts/toolContracts.js +99 -36
  51. package/dist/contracts/toolOutputSchemas.js +154 -82
  52. package/dist/contracts/unknownContractExamples.js +33 -0
  53. package/dist/domain/artifactComparison.js +273 -0
  54. package/dist/domain/artifactGraph.js +194 -0
  55. package/dist/domain/artifactInventoryEvidence.js +150 -0
  56. package/dist/domain/binaryTarget.js +55 -0
  57. package/dist/domain/bundleComparison.js +266 -0
  58. package/dist/domain/callPath.js +346 -0
  59. package/dist/domain/changedBehavior.js +294 -0
  60. package/dist/domain/errors.js +192 -4
  61. package/dist/domain/evidence.js +41 -10
  62. package/dist/domain/evidenceBundle.js +187 -6
  63. package/dist/domain/functionComparison.js +201 -0
  64. package/dist/domain/functionComparisonNormalization.js +112 -0
  65. package/dist/domain/functionComparisonResults.js +54 -0
  66. package/dist/domain/functionComparisonSchemas.js +82 -0
  67. package/dist/domain/functionDossierEvidence.js +171 -0
  68. package/dist/domain/hopperValues.js +14 -7
  69. package/dist/domain/nativeInspection.js +142 -0
  70. package/dist/domain/processCapture.js +152 -54
  71. package/dist/domain/processComparison.js +106 -0
  72. package/dist/domain/reconstructionUnknowns.js +90 -0
  73. package/dist/domain/reconstructionVerification.js +285 -0
  74. package/dist/domain/reconstructionVerificationSchemas.js +126 -0
  75. package/dist/domain/referenceSourceClassification.js +496 -0
  76. package/dist/domain/referenceSourceGraph.js +376 -0
  77. package/dist/domain/referenceSourceImportParsing.js +235 -0
  78. package/dist/domain/referenceSourcePolicy.js +1 -0
  79. package/dist/domain/residualUnknown.js +239 -0
  80. package/dist/domain/staticRuntimeCorrelation.js +375 -0
  81. package/dist/hopper/BridgeLauncher.js +40 -3
  82. package/dist/hopper/HopperClient.js +14 -5
  83. package/dist/hopper/HopperProvider.js +57 -22
  84. package/dist/identity.js +1 -0
  85. package/dist/main.js +5 -1
  86. package/dist/native/CommandRunner.js +156 -0
  87. package/dist/native/NativeMacOSProvider.js +306 -0
  88. package/dist/native/NativeMachoInspection.js +135 -0
  89. package/dist/native/parsers/codesign.js +55 -0
  90. package/dist/native/parsers/demangle.js +26 -0
  91. package/dist/native/parsers/dyldInfo.js +25 -0
  92. package/dist/native/parsers/lipo.js +67 -0
  93. package/dist/native/parsers/otool.js +193 -0
  94. package/dist/native/parsers/plist.js +23 -0
  95. package/dist/reference/ReferenceSourceReader.js +73 -0
  96. package/dist/reference/ReferenceSourceReaderEntries.js +206 -0
  97. package/dist/reference/ReferenceSourceReaderErrors.js +19 -0
  98. package/dist/reference/ReferenceSourceReaderFile.js +119 -0
  99. package/dist/reference/ReferenceSourceReaderPaths.js +23 -0
  100. package/dist/reference/ReferenceSourceReaderTypes.js +2 -0
  101. package/dist/reference/ReferenceSourceReaderValidate.js +71 -0
  102. package/dist/server/createServer.js +31 -5
  103. package/dist/server/recordDerivedEvidence.js +10 -0
  104. package/dist/server/registerArtifactComparisonTool.js +62 -0
  105. package/dist/server/registerArtifactTools.js +6 -0
  106. package/dist/server/registerBundleComparisonTool.js +47 -0
  107. package/dist/server/registerEnhancedTools.js +64 -12
  108. package/dist/server/registerEvidenceTools.js +36 -0
  109. package/dist/server/registerFunctionComparisonTool.js +68 -0
  110. package/dist/server/registerInvestigationTools.js +224 -0
  111. package/dist/server/registerNativeTools.js +6 -0
  112. package/dist/server/registerOfficialTools.js +47 -14
  113. package/dist/server/registerProcessComparisonTool.js +106 -0
  114. package/dist/server/registerSessionTools.js +179 -70
  115. package/dist/server/sessionEvidence.js +28 -0
  116. package/dist/server/sessionToolPolicies.js +64 -0
  117. package/dist/server/toolRegistrationOptions.js +7 -0
  118. package/dist/server/toolResult.js +8 -5
  119. package/install.sh +198 -0
  120. package/package.json +18 -1
  121. package/scripts/rea.mjs +5 -1
  122. package/skills/rea-analysis/SKILL.md +77 -2
@@ -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 { err, ok } from "../domain/result.js";
6
- import { EvidenceLedgerError } from "../domain/errors.js";
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
- const DENY_PROCESS_POLICY = {
12
- enabled: false,
13
- executableRoots: [],
14
- workingRoots: [],
15
- allowedEnvironment: [],
16
- };
17
- const PROCESS_PROVIDER = {
18
- id: "rea-process",
19
- name: "REA deterministic process harness",
20
- version: "1",
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 registerProcessTools = ({ server, session, logger, processPolicy, captureContract, compareContract, }) => {
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/v1",
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
- }, () => toCallToolResult(ok(session.exportEvidenceBundle()), exportContract));
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
- try {
99
- const bundle = z.object({ bundle: z.unknown() }).parse(input).bundle;
100
- const imported = session.importEvidenceBundle(bundle);
101
- return toCallToolResult(ok({
102
- imported,
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
- catch (cause) {
107
- return toCallToolResult(err(new EvidenceLedgerError("Evidence bundle validation failed", {
108
- cause,
109
- })), importContract);
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
- /** Register MCP-only target lifecycle operations on a long-lived session. */
114
- export const registerSessionTools = (server, session, logger, processPolicy = DENY_PROCESS_POLICY) => {
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
- registerEvidenceTools({ server, session, exportContract, importContract });
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
- compareContract,
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
- ? successResult(result.value, contract)
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: `${result.error._tag}: ${result.error.message}`,
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: "HopperProtocolError: Analysis output does not match the tool contract",
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