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.
- package/README.md +95 -27
- package/bridge/hopper_bridge.py +172 -14
- package/dist/application/AnalysisSnapshotCache.js +131 -0
- package/dist/application/AnalysisSnapshotFiles.js +31 -0
- package/dist/application/ArtifactGraphConstruction.js +1 -1
- package/dist/application/ArtifactInventory.js +100 -24
- package/dist/application/AuthorizedArtifactInventory.js +16 -0
- package/dist/application/BinarySession.js +149 -36
- package/dist/application/BinarySessionPort.js +1 -0
- package/dist/application/BoundedJsonFiles.js +109 -0
- package/dist/application/CapabilityInventory.js +127 -0
- package/dist/application/ClientRegistrationStatus.js +69 -0
- package/dist/application/CrossVersionInventory.js +109 -0
- package/dist/application/CrossVersionInvestigation.js +358 -0
- package/dist/application/DirectAnalysis.js +205 -24
- package/dist/application/Doctor.js +104 -3
- package/dist/application/EvidenceBundleFiles.js +15 -108
- package/dist/application/EvidenceLedger.js +3 -2
- package/dist/application/InvestigationProviders.js +48 -0
- package/dist/application/InvestigationWorkspaceStore.js +214 -0
- package/dist/application/LinuxHopper.js +72 -48
- package/dist/application/PermissionAuthority.js +183 -0
- package/dist/application/PermissionConfiguration.js +26 -0
- package/dist/application/ProcessCaptureAuthority.js +2 -2
- package/dist/application/ProcessCaptureError.js +25 -0
- package/dist/application/ProcessCaptureLifecycle.js +22 -2
- package/dist/application/ProcessCli.js +111 -31
- package/dist/application/ProcessHarness.js +6 -3
- package/dist/application/ProgressReporter.js +30 -0
- package/dist/application/ProjectPermissionStore.js +110 -0
- package/dist/application/ReferenceSourceImportEntries.js +18 -3
- package/dist/application/ReferenceSourceImportTypes.js +32 -0
- package/dist/application/Setup.js +35 -101
- package/dist/application/SetupClients.js +19 -0
- package/dist/application/SetupInstallFailure.js +53 -0
- package/dist/application/SetupPlan.js +31 -0
- package/dist/application/SupportedClients.js +41 -0
- package/dist/application/Uninstall.js +34 -24
- package/dist/application/UnknownEvidence.js +33 -0
- package/dist/application/Upgrade.js +3 -1
- package/dist/application/runtime.js +2 -2
- package/dist/artifacts/ArtifactProvider.js +15 -8
- package/dist/catalogIdentity.js +121 -0
- package/dist/cli.js +76 -13
- package/dist/cliEvidenceCommands.js +52 -12
- package/dist/cliInvestigationCommands.js +137 -0
- package/dist/cliLogging.js +20 -3
- package/dist/cliOutput.js +41 -0
- package/dist/cliPolicyCommands.js +115 -0
- package/dist/config.js +102 -27
- package/dist/contracts/artifactToolContracts.js +14 -1
- package/dist/contracts/errorSchemas.js +98 -0
- package/dist/contracts/promptContracts.js +256 -0
- package/dist/contracts/sessionLifecycleInputs.js +11 -0
- package/dist/contracts/toolContractTypes.js +1 -0
- package/dist/contracts/toolContracts.js +20 -7
- package/dist/contracts/toolOutputSchemas.js +70 -9
- package/dist/domain/analysisSnapshot.js +149 -0
- package/dist/domain/artifactComparison.js +38 -11
- package/dist/domain/artifactGraph.js +25 -1
- package/dist/domain/artifactInventoryEvidence.js +18 -0
- package/dist/domain/changedBehavior.js +35 -10
- package/dist/domain/errors.js +384 -32
- package/dist/domain/evidence.js +4 -2
- package/dist/domain/evidenceBundle.js +28 -0
- package/dist/domain/hopperStartupFailure.js +55 -0
- package/dist/domain/investigationWorkspace.js +341 -0
- package/dist/domain/jsonValue.js +2 -0
- package/dist/domain/nativeInspection.js +3 -2
- package/dist/domain/permissionPolicy.js +157 -0
- package/dist/domain/processCapture.js +5 -4
- package/dist/domain/processComparison.js +6 -4
- package/dist/domain/reconstructionVerification.js +1 -1
- package/dist/domain/reconstructionVerificationSchemas.js +1 -0
- package/dist/domain/staticRuntimeCorrelation.js +5 -1
- package/dist/generatedPackageMetadata.js +9 -0
- package/dist/hopper/BridgeLauncher.js +51 -20
- package/dist/hopper/HopperClient.js +45 -3
- package/dist/hopper/HopperProvider.js +1 -0
- package/dist/identity.js +10 -3
- package/dist/logger.js +2 -2
- package/dist/main.js +139 -28
- package/dist/server/createServer.js +54 -5
- package/dist/server/mcpProgress.js +23 -0
- package/dist/server/promptCompletion.js +144 -0
- package/dist/server/registerArtifactComparisonTool.js +6 -2
- package/dist/server/registerBundleComparisonTool.js +6 -2
- package/dist/server/registerEnhancedTools.js +20 -7
- package/dist/server/registerEvidenceResources.js +302 -0
- package/dist/server/registerEvidenceTools.js +59 -8
- package/dist/server/registerFunctionComparisonTool.js +7 -3
- package/dist/server/registerInvestigationTools.js +111 -18
- package/dist/server/registerOfficialTools.js +25 -14
- package/dist/server/registerProcessComparisonTool.js +8 -10
- package/dist/server/registerPrompts.js +67 -0
- package/dist/server/registerSessionStatusTool.js +52 -0
- package/dist/server/registerSessionTools.js +178 -22
- package/dist/server/runDerivedOperation.js +35 -0
- package/dist/server/sessionEvidence.js +2 -8
- package/dist/server/sessionToolPolicies.js +1 -42
- package/dist/server/toolResult.js +34 -6
- package/dist/serverIdentity.js +70 -0
- package/install.sh +11 -11
- package/package.json +10 -6
- package/scripts/hopper-demo-x11.py +349 -0
- package/scripts/rea.mjs +6 -3
- 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 {
|
|
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:
|
|
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 =
|
|
175
|
-
|
|
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 () =>
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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(
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
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 ||
|
|
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 &&
|
|
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
|
-
|
|
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
|
|
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:
|
|
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: "
|
|
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: [
|
|
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
|
|
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 "
|
|
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
|
|
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 "
|
|
76
|
-
[[ "$tag" =~ ^rea-agents-([0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?)$ ]] || fail "latest release tag
|
|
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
|
|
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
|
|
103
|
-
[[ -x "$install_bin" ]] || fail "the rea command
|
|
104
|
-
installed_version="$("$install_bin" --version 2>/dev/null | tr -d '[:space:]')" || fail "could not read the
|
|
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
|
|
112
|
+
printf 'Run %s setup to configure Hopper and agents.\n' "$install_bin"
|
|
113
113
|
fi
|