rea-agents 1.2.0 → 1.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 (107) hide show
  1. package/README.md +95 -27
  2. package/bridge/hopper_bridge.py +172 -14
  3. package/dist/application/AnalysisSnapshotCache.js +131 -0
  4. package/dist/application/AnalysisSnapshotFiles.js +31 -0
  5. package/dist/application/ArtifactGraphConstruction.js +1 -1
  6. package/dist/application/ArtifactInventory.js +100 -24
  7. package/dist/application/AuthorizedArtifactInventory.js +16 -0
  8. package/dist/application/BinarySession.js +149 -36
  9. package/dist/application/BinarySessionPort.js +1 -0
  10. package/dist/application/BoundedJsonFiles.js +109 -0
  11. package/dist/application/CapabilityInventory.js +127 -0
  12. package/dist/application/ClientRegistrationStatus.js +69 -0
  13. package/dist/application/CrossVersionInventory.js +109 -0
  14. package/dist/application/CrossVersionInvestigation.js +358 -0
  15. package/dist/application/DirectAnalysis.js +205 -24
  16. package/dist/application/Doctor.js +104 -3
  17. package/dist/application/EvidenceBundleFiles.js +15 -108
  18. package/dist/application/EvidenceLedger.js +3 -2
  19. package/dist/application/InvestigationProviders.js +48 -0
  20. package/dist/application/InvestigationWorkspaceStore.js +214 -0
  21. package/dist/application/LinuxHopper.js +72 -48
  22. package/dist/application/PermissionAuthority.js +183 -0
  23. package/dist/application/PermissionConfiguration.js +26 -0
  24. package/dist/application/ProcessCaptureAuthority.js +2 -2
  25. package/dist/application/ProcessCaptureError.js +25 -0
  26. package/dist/application/ProcessCaptureLifecycle.js +22 -2
  27. package/dist/application/ProcessCli.js +111 -31
  28. package/dist/application/ProcessHarness.js +6 -3
  29. package/dist/application/ProgressReporter.js +30 -0
  30. package/dist/application/ProjectPermissionStore.js +110 -0
  31. package/dist/application/ReferenceSourceImportEntries.js +18 -3
  32. package/dist/application/ReferenceSourceImportTypes.js +32 -0
  33. package/dist/application/Setup.js +35 -101
  34. package/dist/application/SetupClients.js +19 -0
  35. package/dist/application/SetupInstallFailure.js +53 -0
  36. package/dist/application/SetupPlan.js +31 -0
  37. package/dist/application/SupportedClients.js +41 -0
  38. package/dist/application/Uninstall.js +34 -24
  39. package/dist/application/UnknownEvidence.js +33 -0
  40. package/dist/application/Upgrade.js +3 -1
  41. package/dist/application/runtime.js +2 -2
  42. package/dist/artifacts/ArtifactProvider.js +15 -8
  43. package/dist/catalogIdentity.js +121 -0
  44. package/dist/cli.js +76 -13
  45. package/dist/cliEvidenceCommands.js +52 -12
  46. package/dist/cliInvestigationCommands.js +137 -0
  47. package/dist/cliLogging.js +20 -3
  48. package/dist/cliOutput.js +41 -0
  49. package/dist/cliPolicyCommands.js +115 -0
  50. package/dist/config.js +102 -27
  51. package/dist/contracts/artifactToolContracts.js +14 -1
  52. package/dist/contracts/errorSchemas.js +98 -0
  53. package/dist/contracts/promptContracts.js +256 -0
  54. package/dist/contracts/sessionLifecycleInputs.js +11 -0
  55. package/dist/contracts/toolContractTypes.js +1 -0
  56. package/dist/contracts/toolContracts.js +20 -7
  57. package/dist/contracts/toolOutputSchemas.js +70 -9
  58. package/dist/domain/analysisSnapshot.js +149 -0
  59. package/dist/domain/artifactComparison.js +38 -11
  60. package/dist/domain/artifactGraph.js +25 -1
  61. package/dist/domain/artifactInventoryEvidence.js +18 -0
  62. package/dist/domain/changedBehavior.js +35 -10
  63. package/dist/domain/errors.js +384 -32
  64. package/dist/domain/evidence.js +4 -2
  65. package/dist/domain/evidenceBundle.js +28 -0
  66. package/dist/domain/hopperStartupFailure.js +55 -0
  67. package/dist/domain/investigationWorkspace.js +341 -0
  68. package/dist/domain/jsonValue.js +2 -0
  69. package/dist/domain/nativeInspection.js +3 -2
  70. package/dist/domain/permissionPolicy.js +157 -0
  71. package/dist/domain/processCapture.js +5 -4
  72. package/dist/domain/processComparison.js +6 -4
  73. package/dist/domain/reconstructionVerification.js +1 -1
  74. package/dist/domain/reconstructionVerificationSchemas.js +1 -0
  75. package/dist/domain/staticRuntimeCorrelation.js +5 -1
  76. package/dist/generatedPackageMetadata.js +9 -0
  77. package/dist/hopper/BridgeLauncher.js +51 -20
  78. package/dist/hopper/HopperClient.js +45 -3
  79. package/dist/hopper/HopperProvider.js +1 -0
  80. package/dist/identity.js +10 -3
  81. package/dist/logger.js +2 -2
  82. package/dist/main.js +139 -28
  83. package/dist/server/createServer.js +54 -5
  84. package/dist/server/mcpProgress.js +23 -0
  85. package/dist/server/promptCompletion.js +144 -0
  86. package/dist/server/registerArtifactComparisonTool.js +6 -2
  87. package/dist/server/registerBundleComparisonTool.js +6 -2
  88. package/dist/server/registerEnhancedTools.js +20 -7
  89. package/dist/server/registerEvidenceResources.js +302 -0
  90. package/dist/server/registerEvidenceTools.js +59 -8
  91. package/dist/server/registerFunctionComparisonTool.js +7 -3
  92. package/dist/server/registerInvestigationTools.js +111 -18
  93. package/dist/server/registerOfficialTools.js +25 -14
  94. package/dist/server/registerProcessComparisonTool.js +8 -10
  95. package/dist/server/registerPrompts.js +67 -0
  96. package/dist/server/registerSessionStatusTool.js +52 -0
  97. package/dist/server/registerSessionTools.js +178 -22
  98. package/dist/server/runDerivedOperation.js +35 -0
  99. package/dist/server/sessionEvidence.js +2 -8
  100. package/dist/server/sessionToolPolicies.js +1 -42
  101. package/dist/server/toolResult.js +34 -6
  102. package/dist/serverIdentity.js +70 -0
  103. package/install.sh +11 -11
  104. package/package.json +10 -6
  105. package/scripts/hopper-demo-x11.py +349 -0
  106. package/scripts/rea.mjs +6 -3
  107. package/skills/rea-analysis/SKILL.md +9 -2
@@ -2,12 +2,13 @@ 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 { ok } from "../domain/result.js";
5
+ import { err, ok } from "../domain/result.js";
6
6
  import { processScenarioSchema } from "../domain/processCapture.js";
7
7
  import { captureProcessScenario } from "../application/ProcessHarness.js";
8
8
  import { recordUnknownInputSchema, updateUnknownInputSchema, } from "../domain/residualUnknown.js";
9
9
  import { readEvidenceBundle, writeEvidenceBundle, } from "../application/EvidenceBundleFiles.js";
10
- import { UnknownRegistryError } from "../domain/errors.js";
10
+ import { readAnalysisSnapshot, writeAnalysisSnapshot, } from "../application/AnalysisSnapshotFiles.js";
11
+ import { AnalysisProtocolError, PermissionRequiredError, UnknownRegistryError, } from "../domain/errors.js";
11
12
  import { DENY_EVIDENCE_FILE_POLICY, DENY_PROCESS_POLICY, } from "./sessionToolPolicies.js";
12
13
  import { createProcessCaptureEvidence } from "../application/ProcessEvidence.js";
13
14
  import { registerProcessComparisonTool } from "./registerProcessComparisonTool.js";
@@ -15,13 +16,25 @@ import { registerArtifactComparisonTool } from "./registerArtifactComparisonTool
15
16
  import { registerFunctionComparisonTool } from "./registerFunctionComparisonTool.js";
16
17
  import { registerBundleComparisonTool } from "./registerBundleComparisonTool.js";
17
18
  import { registerInvestigationTools } from "./registerInvestigationTools.js";
19
+ import { closeBinaryInputSchema, openBinaryInputSchema, } from "../contracts/sessionLifecycleInputs.js";
20
+ import { registerSessionStatusTool } from "./registerSessionStatusTool.js";
21
+ import { mcpProgressReporter } from "./mcpProgress.js";
22
+ const permissionFailure = (failure) => {
23
+ if (failure.ok)
24
+ return err(new AnalysisProtocolError("Expected a denied permission"));
25
+ return err(failure.error instanceof PermissionRequiredError
26
+ ? failure.error
27
+ : new AnalysisProtocolError(failure.error.message, {
28
+ cause: failure.error,
29
+ }));
30
+ };
18
31
  const recordProcessResidualUnknowns = (session, scenario, evidence, residuals) => {
19
32
  if (scenario.unknown_registry_approved !== true)
20
33
  return ok(null);
21
34
  for (const residual of residuals) {
22
35
  const unknown = session.recordUnknown({
23
36
  approved: true,
24
- question: `${residual.scope}: ${residual.reason}`,
37
+ question: `Was ${residual.scope} behavior fully observed during capture?`,
25
38
  severity: "medium",
26
39
  domain: `process-${residual.scope}`,
27
40
  supporting_evidence_ids: [evidence.evidence_id],
@@ -44,7 +57,7 @@ const recordProcessResidualUnknowns = (session, scenario, evidence, residuals) =
44
57
  }
45
58
  return ok(null);
46
59
  };
47
- const registerProcessTools = ({ server, session, logger, processPolicy, captureContract, }) => {
60
+ const registerProcessTools = ({ server, session, logger, processPolicy, captureContract, permissionAuthority, }) => {
48
61
  server.registerTool(captureContract.name, {
49
62
  description: captureContract.description,
50
63
  inputSchema: captureContract.inputSchema,
@@ -52,7 +65,37 @@ const registerProcessTools = ({ server, session, logger, processPolicy, captureC
52
65
  annotations: captureContract.annotations,
53
66
  }, async (input, context) => {
54
67
  const scenario = processScenarioSchema.parse(input);
68
+ if (permissionAuthority !== undefined) {
69
+ const authorized = await permissionAuthority.authorize({
70
+ capability: "process_capture",
71
+ roots: [scenario.working_directory, ...scenario.filesystem_roots],
72
+ executables: [scenario.executable],
73
+ environment_names: [
74
+ ...Object.keys(scenario.environment),
75
+ ...scenario.inherit_environment,
76
+ ],
77
+ network: scenario.network_access === "host" ? "external" : "none",
78
+ mount: false,
79
+ operation_identity: `capture_process_scenario:${scenario.executable}`,
80
+ }, "read");
81
+ if (!authorized.ok)
82
+ return toCallToolResult(permissionFailure(authorized), captureContract);
83
+ }
84
+ const progress = mcpProgressReporter(context);
85
+ await progress.report({
86
+ phase: captureContract.name,
87
+ completed: 0,
88
+ total: 1,
89
+ message: "started",
90
+ });
55
91
  const captured = await logToolExecution(logger, captureContract.name, () => captureProcessScenario(scenario, processPolicy, context.mcpReq.signal));
92
+ await progress.report({
93
+ phase: captureContract.name,
94
+ completed: 1,
95
+ total: 1,
96
+ message: captured.ok ? "completed" : "failed",
97
+ terminal: true,
98
+ });
56
99
  if (!captured.ok)
57
100
  return toCallToolResult(captured, captureContract);
58
101
  const evidence = createProcessCaptureEvidence(scenario, captured.value);
@@ -65,7 +108,7 @@ const registerProcessTools = ({ server, session, logger, processPolicy, captureC
65
108
  return toCallToolResult(ok(evidence), captureContract);
66
109
  });
67
110
  };
68
- const registerEvidenceTools = ({ server, session, exportContract, importContract, filePolicy, }) => {
111
+ const registerEvidenceTools = ({ server, session, exportContract, importContract, filePolicy, permissionAuthority, }) => {
69
112
  server.registerTool(exportContract.name, {
70
113
  description: exportContract.description,
71
114
  inputSchema: exportContract.inputSchema,
@@ -81,6 +124,19 @@ const registerEvidenceTools = ({ server, session, exportContract, importContract
81
124
  const bundle = session.exportEvidenceBundle();
82
125
  if (parsed.path === undefined)
83
126
  return toCallToolResult(ok(bundle), exportContract);
127
+ if (permissionAuthority !== undefined) {
128
+ const authorized = await permissionAuthority.authorize({
129
+ capability: "evidence_write",
130
+ roots: [parsed.path],
131
+ executables: [],
132
+ environment_names: [],
133
+ network: "none",
134
+ mount: false,
135
+ operation_identity: `export_evidence:${parsed.path}`,
136
+ }, "write");
137
+ if (!authorized.ok)
138
+ return toCallToolResult(permissionFailure(authorized), exportContract);
139
+ }
84
140
  const written = await writeEvidenceBundle(bundle, parsed.path, parsed.overwrite, filePolicy);
85
141
  return written.ok
86
142
  ? toCallToolResult(ok({
@@ -97,6 +153,19 @@ const registerEvidenceTools = ({ server, session, exportContract, importContract
97
153
  annotations: importContract.annotations,
98
154
  }, async (input) => {
99
155
  const path = z.object({ path: z.string().min(1) }).parse(input).path;
156
+ if (permissionAuthority !== undefined) {
157
+ const authorized = await permissionAuthority.authorize({
158
+ capability: "evidence_read",
159
+ roots: [path],
160
+ executables: [],
161
+ environment_names: [],
162
+ network: "none",
163
+ mount: false,
164
+ operation_identity: `import_evidence:${path}`,
165
+ }, "read");
166
+ if (!authorized.ok)
167
+ return toCallToolResult(permissionFailure(authorized), importContract);
168
+ }
100
169
  const loaded = await readEvidenceBundle(path, filePolicy);
101
170
  if (!loaded.ok)
102
171
  return toCallToolResult(loaded, importContract);
@@ -163,7 +232,7 @@ const registerUnknownTools = ({ server, session, contracts: [listContract, recor
163
232
  return toCallToolResult(session.verifyUnknownResolution(unknownId), verifyContract);
164
233
  });
165
234
  };
166
- const registerLifecycleTools = (server, session, logger, contracts) => {
235
+ const registerLifecycleTools = ({ server, session, logger, contracts, snapshotFilePolicy, startedAt, availabilityPolicy, permissionAuthority, }) => {
167
236
  const [openContract, closeContract, statusContract] = contracts;
168
237
  server.registerTool(openContract.name, {
169
238
  description: openContract.description,
@@ -171,8 +240,33 @@ const registerLifecycleTools = (server, session, logger, contracts) => {
171
240
  outputSchema: openContract.outputSchema,
172
241
  annotations: openContract.annotations,
173
242
  }, async (input, context) => {
174
- const parsed = z.object({ path: z.string().min(1) }).parse(input);
175
- const opened = await logToolExecution(logger, openContract.name, () => session.open(parsed.path, { signal: context.mcpReq.signal }));
243
+ const parsed = openBinaryInputSchema.parse(input);
244
+ let snapshot;
245
+ if (parsed.snapshot_path !== undefined) {
246
+ if (permissionAuthority !== undefined) {
247
+ const authorized = await permissionAuthority.authorize({
248
+ capability: "snapshot_read",
249
+ roots: [parsed.snapshot_path],
250
+ executables: [],
251
+ environment_names: [],
252
+ network: "none",
253
+ mount: false,
254
+ operation_identity: `open_binary:snapshot:${parsed.snapshot_path}`,
255
+ }, "read");
256
+ if (!authorized.ok)
257
+ return toCallToolResult(permissionFailure(authorized), openContract);
258
+ }
259
+ const loaded = await readAnalysisSnapshot(parsed.snapshot_path, snapshotFilePolicy);
260
+ if (!loaded.ok)
261
+ return toCallToolResult(loaded, openContract);
262
+ snapshot = loaded.value;
263
+ }
264
+ const opened = await logToolExecution(logger, openContract.name, () => session.open(parsed.path, {
265
+ signal: context.mcpReq.signal,
266
+ ...(snapshot === undefined ? {} : { snapshot }),
267
+ }));
268
+ if (opened.ok)
269
+ server.sendToolListChanged();
176
270
  return opened.ok
177
271
  ? toCallToolResult({
178
272
  ok: true,
@@ -192,29 +286,76 @@ const registerLifecycleTools = (server, session, logger, contracts) => {
192
286
  inputSchema: closeContract.inputSchema,
193
287
  outputSchema: closeContract.outputSchema,
194
288
  annotations: closeContract.annotations,
195
- }, async () => toCallToolResult(await logToolExecution(logger, closeContract.name, () => session.close()), closeContract));
196
- server.registerTool(statusContract.name, {
197
- description: statusContract.description,
198
- inputSchema: statusContract.inputSchema,
199
- outputSchema: statusContract.outputSchema,
200
- annotations: statusContract.annotations,
201
- }, () => toCallToolResult({ ok: true, value: session.status() }, statusContract));
289
+ }, async (input) => {
290
+ const parsed = closeBinaryInputSchema.parse(input);
291
+ if (parsed.snapshot_path !== undefined) {
292
+ if (permissionAuthority !== undefined) {
293
+ const authorized = await permissionAuthority.authorize({
294
+ capability: "snapshot_write",
295
+ roots: [parsed.snapshot_path],
296
+ executables: [],
297
+ environment_names: [],
298
+ network: "none",
299
+ mount: false,
300
+ operation_identity: `close_binary:snapshot:${parsed.snapshot_path}`,
301
+ }, "write");
302
+ if (!authorized.ok)
303
+ return toCallToolResult(permissionFailure(authorized), closeContract);
304
+ }
305
+ const snapshot = session.exportAnalysisSnapshot();
306
+ if (!snapshot.ok)
307
+ return toCallToolResult(snapshot, closeContract);
308
+ const written = await writeAnalysisSnapshot(snapshot.value, parsed.snapshot_path, parsed.overwrite, snapshotFilePolicy);
309
+ if (!written.ok)
310
+ return toCallToolResult(written, closeContract);
311
+ const closed = await logToolExecution(logger, closeContract.name, () => session.close());
312
+ if (closed.ok)
313
+ server.sendToolListChanged();
314
+ return closed.ok
315
+ ? toCallToolResult(ok({
316
+ path: written.value.path,
317
+ bytes: written.value.bytes,
318
+ entries: snapshot.value.entries.length,
319
+ }), closeContract)
320
+ : toCallToolResult(closed, closeContract);
321
+ }
322
+ const closed = await logToolExecution(logger, closeContract.name, () => session.close());
323
+ if (closed.ok)
324
+ server.sendToolListChanged();
325
+ return toCallToolResult(closed, closeContract);
326
+ });
327
+ registerSessionStatusTool(server, session, statusContract, startedAt, availabilityPolicy);
202
328
  };
203
329
  export const registerSessionTools = (server, session, logger, options = {}) => {
204
330
  const processPolicy = options.processPolicy ?? DENY_PROCESS_POLICY;
205
331
  const evidenceFilePolicy = options.evidenceFilePolicy ?? DENY_EVIDENCE_FILE_POLICY;
332
+ const analysisSnapshotFilePolicy = options.analysisSnapshotFilePolicy ?? DENY_EVIDENCE_FILE_POLICY;
206
333
  const [openContract, closeContract, statusContract, exportContract, importContract, captureContract, compareContract, compareArtifactsContract, compareFunctionsContract, compareBundlesContract, changedBehaviorContract, callPathContract, staticRuntimeContract, reconstructionContract, listUnknownContract, recordUnknownContract, updateUnknownContract, verifyUnknownContract,] = SESSION_TOOL_CONTRACTS;
207
- registerLifecycleTools(server, session, logger, [
208
- openContract,
209
- closeContract,
210
- statusContract,
211
- ]);
334
+ registerLifecycleTools({
335
+ server,
336
+ session,
337
+ logger,
338
+ contracts: [openContract, closeContract, statusContract],
339
+ snapshotFilePolicy: analysisSnapshotFilePolicy,
340
+ startedAt: options.startedAt ?? new Date().toISOString(),
341
+ availabilityPolicy: options.availabilityPolicy ??
342
+ (() => ({
343
+ processCaptureEnabled: processPolicy.enabled,
344
+ evidenceFileRoots: evidenceFilePolicy.roots.length,
345
+ })),
346
+ ...(options.permissionAuthority === undefined
347
+ ? {}
348
+ : { permissionAuthority: options.permissionAuthority }),
349
+ });
212
350
  registerEvidenceTools({
213
351
  server,
214
352
  session,
215
353
  exportContract,
216
354
  importContract,
217
355
  filePolicy: evidenceFilePolicy,
356
+ ...(options.permissionAuthority === undefined
357
+ ? {}
358
+ : { permissionAuthority: options.permissionAuthority }),
218
359
  });
219
360
  registerProcessTools({
220
361
  server,
@@ -222,17 +363,32 @@ export const registerSessionTools = (server, session, logger, options = {}) => {
222
363
  logger,
223
364
  processPolicy,
224
365
  captureContract,
366
+ ...(options.permissionAuthority === undefined
367
+ ? {}
368
+ : { permissionAuthority: options.permissionAuthority }),
225
369
  });
226
370
  registerProcessComparisonTool(server, session, compareContract);
227
371
  registerArtifactComparisonTool(server, session, compareArtifactsContract);
228
372
  registerFunctionComparisonTool(server, session, compareFunctionsContract);
229
373
  registerBundleComparisonTool(server, session, compareBundlesContract);
230
- registerInvestigationTools(server, session, [
374
+ const investigationContracts = [
231
375
  changedBehaviorContract,
232
376
  callPathContract,
233
377
  staticRuntimeContract,
234
378
  reconstructionContract,
235
- ]);
379
+ ];
380
+ registerInvestigationTools(server, session, investigationContracts, {
381
+ evidenceFiles: evidenceFilePolicy,
382
+ inputRoots: options.investigationInputRoots ?? [],
383
+ ...(options.artifactIntegrityContinueEnabled === undefined
384
+ ? {}
385
+ : {
386
+ integrityContinueEnabled: options.artifactIntegrityContinueEnabled,
387
+ }),
388
+ ...(options.permissionAuthority === undefined
389
+ ? {}
390
+ : { permissionAuthority: options.permissionAuthority }),
391
+ });
236
392
  registerUnknownTools({
237
393
  server,
238
394
  session,
@@ -0,0 +1,35 @@
1
+ import { setImmediate } from "node:timers/promises";
2
+ import { AnalysisCancelledError } from "../domain/errors.js";
3
+ import { err, ok } from "../domain/result.js";
4
+ import { mcpProgressReporter } from "./mcpProgress.js";
5
+ /** Run synchronous derived work with transport yields and a publication guard. */
6
+ export const runDerivedOperation = async (context, operation, compute) => {
7
+ const progress = mcpProgressReporter(context);
8
+ await progress.report({
9
+ phase: "prepare",
10
+ completed: 0,
11
+ total: 2,
12
+ message: `Preparing ${operation}`,
13
+ });
14
+ await setImmediate();
15
+ if (context.mcpReq.signal.aborted)
16
+ return err(new AnalysisCancelledError(operation));
17
+ const value = compute();
18
+ await progress.report({
19
+ phase: "compute",
20
+ completed: 1,
21
+ total: 2,
22
+ message: `Computed ${operation}`,
23
+ });
24
+ await setImmediate();
25
+ if (context.mcpReq.signal.aborted)
26
+ return err(new AnalysisCancelledError(operation));
27
+ await progress.report({
28
+ phase: "complete",
29
+ completed: 2,
30
+ total: 2,
31
+ message: `Completed ${operation}`,
32
+ terminal: true,
33
+ });
34
+ return ok(value);
35
+ };
@@ -9,7 +9,7 @@ export const resolveSessionEvidence = (session, input) => {
9
9
  for (const item of supplied) {
10
10
  const parsed = parseEvidence(item);
11
11
  const owned = session.evidenceById(parsed.evidence_id);
12
- if (owned === undefined || canonicalJson(owned) !== canonicalJson(parsed))
12
+ if (owned === undefined || canonicalize(owned) !== canonicalize(parsed))
13
13
  return err(new EvidenceIntegrityError("Comparison input does not match its session-owned Evidence"));
14
14
  authoritative.push(owned);
15
15
  }
@@ -18,11 +18,5 @@ export const resolveSessionEvidence = (session, input) => {
18
18
  /** Check exact ownership for a single Evidence record. */
19
19
  export const isSessionEvidence = (session, input) => {
20
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;
21
+ return owned !== undefined && canonicalize(owned) === canonicalize(input);
28
22
  };
@@ -15,45 +15,4 @@ export const DENY_EVIDENCE_FILE_POLICY = {
15
15
  maxNodes: 1_000_000,
16
16
  };
17
17
  export { PROCESS_PROVIDER } from "../application/ProcessEvidence.js";
18
- /** Provider identity for deterministic cross-artifact comparisons. */
19
- export const ARTIFACT_COMPARISON_PROVIDER = {
20
- id: "rea-artifact-comparison",
21
- name: "REA artifact comparison",
22
- version: "1",
23
- };
24
- /** Provider identity for deterministic cross-function comparisons. */
25
- export const FUNCTION_COMPARISON_PROVIDER = {
26
- id: "rea-function-comparison",
27
- name: "REA function comparison",
28
- version: "1",
29
- };
30
- /** Provider identity for deterministic Evidence bundle comparisons. */
31
- export const BUNDLE_COMPARISON_PROVIDER = {
32
- id: "rea-bundle-comparison",
33
- name: "REA Evidence bundle comparison",
34
- version: "1",
35
- };
36
- /** Provider identity for comparison-Evidence behavior aggregation. */
37
- export const CHANGED_BEHAVIOR_PROVIDER = {
38
- id: "rea-changed-behavior",
39
- name: "REA changed behavior investigation",
40
- version: "1",
41
- };
42
- /** Provider identity for Evidence-backed call-path reconstruction. */
43
- export const CALL_PATH_PROVIDER = {
44
- id: "rea-call-path",
45
- name: "REA call path reconstruction",
46
- version: "1",
47
- };
48
- /** Provider identity for explicit static/runtime hypothesis correlation. */
49
- export const STATIC_RUNTIME_PROVIDER = {
50
- id: "rea-static-runtime-correlation",
51
- name: "REA static/runtime correlation",
52
- version: "1",
53
- };
54
- /** Provider identity for finite reconstruction specification verification. */
55
- export const RECONSTRUCTION_PROVIDER = {
56
- id: "rea-reconstruction-verifier",
57
- name: "REA reconstruction verifier",
58
- version: "1",
59
- };
18
+ export { ARTIFACT_COMPARISON_PROVIDER, BUNDLE_COMPARISON_PROVIDER, CALL_PATH_PROVIDER, CHANGED_BEHAVIOR_PROVIDER, FUNCTION_COMPARISON_PROVIDER, RECONSTRUCTION_PROVIDER, STATIC_RUNTIME_PROVIDER, } from "../application/InvestigationProviders.js";
@@ -1,23 +1,26 @@
1
- import { projectAnalysisError } from "../domain/errors.js";
1
+ import { AnalysisOutputError, projectAnalysisError, } from "../domain/errors.js";
2
2
  /**
3
3
  * Translate an application result into MCP text content.
4
4
  * Error tags and safe messages remain visible while underlying causes, process
5
5
  * output, and other potentially sensitive details stay private.
6
6
  */
7
- export const toCallToolResult = (result, contract) => result.ok ? successResult(result.value, contract) : errorResult(result.error);
7
+ export const toCallToolResult = (result, contract, options = {}) => result.ok
8
+ ? successResult(result.value, contract, options)
9
+ : errorResult(result.error);
8
10
  const errorResult = (error) => {
9
11
  const projected = projectAnalysisError(error);
10
12
  return {
11
13
  content: [
12
14
  {
13
15
  type: "text",
14
- text: `${projected.tag}: ${projected.message}`,
16
+ text: projected.message,
15
17
  },
16
18
  ],
19
+ structuredContent: { error: projected },
17
20
  isError: true,
18
21
  };
19
22
  };
20
- const successResult = (value, contract) => {
23
+ const successResult = (value, contract, options) => {
21
24
  const candidate = contract.kind === "session" ? { result: value } : value;
22
25
  const parsed = contract.outputSchema.safeParse(candidate);
23
26
  if (!parsed.success) {
@@ -25,17 +28,42 @@ const successResult = (value, contract) => {
25
28
  content: [
26
29
  {
27
30
  type: "text",
28
- text: "AnalysisOutputError: Analysis output does not match the tool contract",
31
+ text: "Analysis returned an unreadable result. Retry once; if it continues, run `rea doctor`.",
29
32
  },
30
33
  ],
34
+ structuredContent: {
35
+ error: projectAnalysisError(new AnalysisOutputError(contract.name, "output does not match the tool contract")),
36
+ },
31
37
  isError: true,
32
38
  };
33
39
  }
34
40
  return {
35
- content: [{ type: "text", text: formatValue(value) }],
41
+ content: [
42
+ { type: "text", text: formatValue(value) },
43
+ ...evidenceResourceLinks(value, contract.kind === "session" ||
44
+ options.evidenceResourcesAvailable === true),
45
+ ],
36
46
  structuredContent: candidate,
37
47
  };
38
48
  };
49
+ const evidenceResourceLinks = (value, available) => {
50
+ if (!available ||
51
+ typeof value !== "object" ||
52
+ value === null ||
53
+ Array.isArray(value) ||
54
+ typeof value.evidence_id !== "string" ||
55
+ !/^ev_[a-f0-9]{64}$/u.test(value.evidence_id))
56
+ return [];
57
+ return [
58
+ {
59
+ type: "resource_link",
60
+ uri: `rea://evidence/${value.evidence_id}`,
61
+ name: value.evidence_id,
62
+ description: "Session-owned Evidence v2 record",
63
+ mimeType: "application/json",
64
+ },
65
+ ];
66
+ };
39
67
  const formatValue = (value) => {
40
68
  if (value === null)
41
69
  return "OK";
@@ -0,0 +1,70 @@
1
+ import { dirname, basename, resolve } from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { CATALOG_IDENTITY } from "./catalogIdentity.js";
4
+ import { PRODUCT_IDENTITY, SDK_IDENTITY } from "./identity.js";
5
+ const packageRoot = () => {
6
+ const moduleDirectory = dirname(fileURLToPath(import.meta.url));
7
+ return basename(moduleDirectory) === "src" &&
8
+ basename(dirname(moduleDirectory)) === "dist"
9
+ ? resolve(moduleDirectory, "../..")
10
+ : resolve(moduleDirectory, "..");
11
+ };
12
+ /** Build one runtime identity without claiming alignment from absent observations. */
13
+ export const createServerIdentity = (input) => {
14
+ const serverPath = resolve(process.argv[1] ?? "unknown");
15
+ const reasons = [];
16
+ if (input.expected?.package_version !== undefined &&
17
+ input.expected.package_version !== PRODUCT_IDENTITY.packageVersion)
18
+ reasons.push("package_version_mismatch");
19
+ if (input.expected?.catalog_digest !== undefined &&
20
+ input.expected.catalog_digest !== CATALOG_IDENTITY.digests.combined_sha256)
21
+ reasons.push("catalog_digest_mismatch");
22
+ if (input.expected?.server_path !== undefined &&
23
+ resolve(input.expected.server_path) !== serverPath)
24
+ reasons.push("registration_path_mismatch");
25
+ const observedExpectation = input.expected !== undefined &&
26
+ Object.values(input.expected).some((value) => value !== undefined);
27
+ return {
28
+ package: {
29
+ name: PRODUCT_IDENTITY.packageName,
30
+ version: PRODUCT_IDENTITY.packageVersion,
31
+ root_path: packageRoot(),
32
+ build_commit: process.env.REA_BUILD_COMMIT ?? null,
33
+ },
34
+ server: {
35
+ name: PRODUCT_IDENTITY.mcpServerKey,
36
+ version: PRODUCT_IDENTITY.packageVersion,
37
+ started_at: input.startedAt,
38
+ command_path: serverPath,
39
+ },
40
+ sdk: SDK_IDENTITY,
41
+ negotiated_protocol_version: input.protocolVersion ?? null,
42
+ client: input.client === undefined
43
+ ? null
44
+ : { name: input.client.name, version: input.client.version },
45
+ skill: {
46
+ name: PRODUCT_IDENTITY.skillName,
47
+ expected_version: PRODUCT_IDENTITY.skillVersion,
48
+ },
49
+ catalog: CATALOG_IDENTITY,
50
+ protocol_features: {
51
+ progress: true,
52
+ cancellation: true,
53
+ evidence_resources: true,
54
+ elicitation: false,
55
+ },
56
+ alignment: {
57
+ state: reasons.length > 0
58
+ ? "mcp_server_restart_required"
59
+ : observedExpectation
60
+ ? "aligned"
61
+ : "unknown",
62
+ reasons,
63
+ remediation: reasons.length > 0
64
+ ? "Restart the registered MCP server/client, then compare identity again."
65
+ : observedExpectation
66
+ ? null
67
+ : "Supply expected package, catalog, or registration identity to compare the live server.",
68
+ },
69
+ };
70
+ };
package/install.sh CHANGED
@@ -17,7 +17,7 @@ usage() {
17
17
  cat <<'EOF'
18
18
  Usage: install.sh [--version VERSION] [--dry-run] [--no-setup] [--no-prompt] [--verbose]
19
19
 
20
- Installs only the REA CLI. Run `rea setup` separately to configure Hopper and coding agents.
20
+ Installs only the REA CLI. Run `rea setup` separately to configure Hopper and agents.
21
21
  EOF
22
22
  }
23
23
 
@@ -54,13 +54,13 @@ fi
54
54
 
55
55
  platform="$(uname -s)"
56
56
  [[ "$platform" == "Darwin" || "$platform" == "Linux" ]] || fail "REA supports macOS and selected 64-bit Linux distributions."
57
- command -v curl >/dev/null 2>&1 || fail "curl is required."
57
+ command -v curl >/dev/null 2>&1 || fail "curl is required. Install curl, then rerun this installer."
58
58
  command -v node >/dev/null 2>&1 || fail "Node.js 22.19+ or 24.11+ is required; install it from https://nodejs.org and retry."
59
59
  command -v npm >/dev/null 2>&1 || fail "npm is required; install it with Node.js and retry."
60
60
 
61
61
  node_version="$(node -p 'process.versions.node' 2>/dev/null || true)"
62
62
  IFS=. read -r node_major node_minor _node_patch <<<"$node_version"
63
- [[ "$node_major" =~ ^[0-9]+$ && "$node_minor" =~ ^[0-9]+$ ]] || fail "could not determine the active Node.js version."
63
+ [[ "$node_major" =~ ^[0-9]+$ && "$node_minor" =~ ^[0-9]+$ ]] || fail "the active Node.js version could not be read. Check that node works and is on PATH, then retry."
64
64
  if ! ((node_major == 22 && node_minor >= 19 || node_major >= 24)); then
65
65
  fail "Node.js $node_version is unsupported; use Node.js 22.19+ or 24.11+."
66
66
  fi
@@ -71,9 +71,9 @@ fi
71
71
  if [[ -n "$version" ]]; then
72
72
  [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]] || fail "version must be an exact semantic version."
73
73
  else
74
- release_json="$(curl -fsSL --proto '=https' --tlsv1.2 --retry 3 -H 'Accept: application/vnd.github+json' "https://api.github.com/repos/$REPOSITORY/releases/latest")" || fail "could not resolve the latest GitHub release."
75
- 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."
76
- [[ "$tag" =~ ^rea-agents-([0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?)$ ]] || fail "latest release tag is not a valid rea-agents release."
74
+ release_json="$(curl -fsSL --proto '=https' --tlsv1.2 --retry 3 -H 'Accept: application/vnd.github+json' "https://api.github.com/repos/$REPOSITORY/releases/latest")" || fail "the latest REA release could not be resolved. Check network access or pass --version VERSION, then retry."
75
+ 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)})' 2>/dev/null)" || fail "the release response was invalid. Retry later or pass --version VERSION."
76
+ [[ "$tag" =~ ^rea-agents-([0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?)$ ]] || fail "the latest release tag was invalid. Retry later or pass --version VERSION."
77
77
  version="${BASH_REMATCH[1]}"
78
78
  fi
79
79
 
@@ -82,7 +82,7 @@ if [[ "$platform" == "Linux" ]]; then
82
82
  prefix_args=(--prefix "$HOME/.local")
83
83
  install_bin="$HOME/.local/bin/rea"
84
84
  else
85
- npm_prefix="$(npm prefix --global)" || fail "could not determine the npm global prefix."
85
+ npm_prefix="$(npm prefix --global)" || fail "the npm global prefix could not be read. Repair the npm configuration, then retry."
86
86
  install_bin="$npm_prefix/bin/rea"
87
87
  fi
88
88
 
@@ -99,9 +99,9 @@ if [[ "$dry_run" == true ]]; then
99
99
  fi
100
100
 
101
101
  printf 'Installing %s@%s...\n' "$PACKAGE" "$version"
102
- npm install --global "${prefix_args[@]}" "$PACKAGE@$version" || fail "npm package installation failed."
103
- [[ -x "$install_bin" ]] || fail "the rea command was not installed at $install_bin."
104
- installed_version="$("$install_bin" --version 2>/dev/null | tr -d '[:space:]')" || fail "could not read the installed REA version."
102
+ npm install --global "${prefix_args[@]}" "$PACKAGE@$version" || fail "npm could not install REA. Check registry access and npm permissions, then retry."
103
+ [[ -x "$install_bin" ]] || fail "npm completed without installing the rea command. Check the npm global bin directory and PATH, then retry."
104
+ installed_version="$("$install_bin" --version 2>/dev/null | tr -d '[:space:]')" || fail "the installed REA version could not be read. Reinstall the requested version, then retry."
105
105
  [[ "$installed_version" == "$version" ]] || fail "installed version $installed_version does not match $version."
106
106
 
107
107
  printf 'REA %s is installed.\n' "$version"
@@ -109,5 +109,5 @@ if [[ "$start_setup" == true && -r /dev/tty && -w /dev/tty && -t 1 ]]; then
109
109
  printf 'Starting REA setup...\n'
110
110
  "$install_bin" setup </dev/tty >/dev/tty
111
111
  else
112
- printf 'Run %s setup to configure Hopper and coding agents.\n' "$install_bin"
112
+ printf 'Run %s setup to configure Hopper and agents.\n' "$install_bin"
113
113
  fi