rea-agents 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +74 -27
  2. package/bridge/hopper_bridge.py +161 -12
  3. package/dist/application/AnalysisSnapshotCache.js +131 -0
  4. package/dist/application/AnalysisSnapshotFiles.js +31 -0
  5. package/dist/application/ArtifactInventory.js +4 -0
  6. package/dist/application/AuthorizedArtifactInventory.js +16 -0
  7. package/dist/application/BinarySession.js +66 -36
  8. package/dist/application/BinarySessionPort.js +1 -0
  9. package/dist/application/BoundedJsonFiles.js +109 -0
  10. package/dist/application/CrossVersionInventory.js +98 -0
  11. package/dist/application/CrossVersionInvestigation.js +295 -0
  12. package/dist/application/DirectAnalysis.js +115 -22
  13. package/dist/application/Doctor.js +30 -1
  14. package/dist/application/EvidenceBundleFiles.js +15 -108
  15. package/dist/application/EvidenceLedger.js +3 -2
  16. package/dist/application/InvestigationProviders.js +48 -0
  17. package/dist/application/InvestigationWorkspaceStore.js +214 -0
  18. package/dist/application/LinuxHopper.js +72 -48
  19. package/dist/application/ProcessCaptureAuthority.js +2 -2
  20. package/dist/application/ProcessCaptureError.js +12 -0
  21. package/dist/application/ProcessCaptureLifecycle.js +19 -1
  22. package/dist/application/ProcessCli.js +98 -31
  23. package/dist/application/ProcessHarness.js +2 -2
  24. package/dist/application/ReferenceSourceImportEntries.js +18 -3
  25. package/dist/application/ReferenceSourceImportTypes.js +32 -0
  26. package/dist/application/Setup.js +33 -60
  27. package/dist/application/SetupClients.js +19 -0
  28. package/dist/application/SetupInstallFailure.js +53 -0
  29. package/dist/application/SetupPlan.js +31 -0
  30. package/dist/application/Uninstall.js +33 -23
  31. package/dist/application/UnknownEvidence.js +33 -0
  32. package/dist/application/Upgrade.js +1 -1
  33. package/dist/application/runtime.js +1 -1
  34. package/dist/artifacts/ArtifactProvider.js +2 -5
  35. package/dist/cli.js +31 -11
  36. package/dist/cliEvidenceCommands.js +11 -12
  37. package/dist/cliInvestigationCommands.js +86 -0
  38. package/dist/cliOutput.js +41 -0
  39. package/dist/cliProcessCommands.js +14 -4
  40. package/dist/config.js +39 -24
  41. package/dist/contracts/promptContracts.js +256 -0
  42. package/dist/contracts/sessionLifecycleInputs.js +11 -0
  43. package/dist/contracts/toolContractTypes.js +1 -0
  44. package/dist/contracts/toolContracts.js +10 -6
  45. package/dist/contracts/toolOutputSchemas.js +33 -4
  46. package/dist/domain/analysisSnapshot.js +149 -0
  47. package/dist/domain/changedBehavior.js +30 -7
  48. package/dist/domain/errors.js +180 -41
  49. package/dist/domain/evidence.js +4 -2
  50. package/dist/domain/evidenceBundle.js +28 -0
  51. package/dist/domain/hopperStartupFailure.js +55 -0
  52. package/dist/domain/investigationWorkspace.js +214 -0
  53. package/dist/domain/jsonValue.js +2 -0
  54. package/dist/domain/nativeInspection.js +3 -2
  55. package/dist/domain/processCapture.js +5 -4
  56. package/dist/domain/processComparison.js +5 -4
  57. package/dist/hopper/BridgeLauncher.js +47 -20
  58. package/dist/hopper/HopperClient.js +17 -2
  59. package/dist/hopper/HopperProvider.js +1 -0
  60. package/dist/main.js +47 -24
  61. package/dist/server/createServer.js +2 -0
  62. package/dist/server/promptCompletion.js +144 -0
  63. package/dist/server/registerEnhancedTools.js +2 -6
  64. package/dist/server/registerEvidenceTools.js +2 -8
  65. package/dist/server/registerFunctionComparisonTool.js +1 -1
  66. package/dist/server/registerInvestigationTools.js +31 -11
  67. package/dist/server/registerOfficialTools.js +6 -13
  68. package/dist/server/registerProcessComparisonTool.js +1 -7
  69. package/dist/server/registerPrompts.js +67 -0
  70. package/dist/server/registerSessionStatusTool.js +11 -0
  71. package/dist/server/registerSessionTools.js +52 -18
  72. package/dist/server/sessionEvidence.js +2 -8
  73. package/dist/server/sessionToolPolicies.js +1 -42
  74. package/dist/server/toolResult.js +7 -3
  75. package/install.sh +11 -11
  76. package/package.json +5 -3
  77. package/scripts/hopper-demo-x11.py +349 -0
  78. package/scripts/rea.mjs +6 -3
  79. package/skills/rea-analysis/SKILL.md +9 -2
@@ -0,0 +1,144 @@
1
+ import { z } from "zod";
2
+ import { artifactInventoryResultSchema } from "../domain/artifactGraph.js";
3
+ import { parseAddressedPage } from "../domain/hopperValues.js";
4
+ import { processCaptureSchema, validateProcessCapture, } from "../domain/processCapture.js";
5
+ const COMPLETION_SCAN_LIMIT = 10_000;
6
+ const PROCEDURE_PAGE_SIZE = 500;
7
+ const MAX_COMPLETION_VALUE_LENGTH = 4_096;
8
+ const documentListSchema = z.array(z.string().min(1));
9
+ const providerStatusSchema = z.object({
10
+ providers: z.array(z.object({ id: z.string().min(1) })),
11
+ });
12
+ /** Build bounded live completion over the active provider and session ledger. */
13
+ export const createPromptCompletionSource = (analysis, session) => ({
14
+ async complete(kind, value, context) {
15
+ if (value.length > MAX_COMPLETION_VALUE_LENGTH)
16
+ return [];
17
+ const candidates = await completionCandidates(kind, analysis, session, context);
18
+ const prefix = normalize(value);
19
+ return uniqueSorted(candidates)
20
+ .filter((candidate) => normalize(candidate).startsWith(prefix))
21
+ .slice(0, COMPLETION_SCAN_LIMIT);
22
+ },
23
+ });
24
+ const completionCandidates = async (kind, analysis, session, context) => {
25
+ switch (kind) {
26
+ case "document":
27
+ return documentCandidates(analysis);
28
+ case "procedure":
29
+ return procedureCandidates(analysis, context?.arguments?.document);
30
+ case "provider":
31
+ return providerCandidates(session);
32
+ case "evidence":
33
+ case "capture":
34
+ case "manifest":
35
+ case "occurrence":
36
+ return evidenceCandidates(kind, session);
37
+ case "unknown":
38
+ return unknownCandidates(session);
39
+ }
40
+ };
41
+ const documentCandidates = async (analysis) => {
42
+ const result = await analysis.execute("list_documents", {});
43
+ if (!result.ok)
44
+ return [];
45
+ const parsed = documentListSchema.safeParse(result.value.result);
46
+ return parsed.success ? parsed.data.slice(0, COMPLETION_SCAN_LIMIT) : [];
47
+ };
48
+ const procedureCandidates = async (analysis, document) => {
49
+ if (document !== undefined && document.length > MAX_COMPLETION_VALUE_LENGTH)
50
+ return [];
51
+ const byAddress = new Map();
52
+ let offset = 0;
53
+ let exhaustive = false;
54
+ while (byAddress.size < COMPLETION_SCAN_LIMIT / 2) {
55
+ const result = await analysis.execute("list_procedures", {
56
+ offset,
57
+ limit: PROCEDURE_PAGE_SIZE,
58
+ ...(document === undefined || document.length === 0 ? {} : { document }),
59
+ });
60
+ if (!result.ok)
61
+ break;
62
+ const parsed = parseAddressedPage(result.value.result);
63
+ if (!parsed.ok)
64
+ break;
65
+ for (const item of parsed.value.items) {
66
+ if (byAddress.size >= COMPLETION_SCAN_LIMIT / 2)
67
+ break;
68
+ if (!byAddress.has(item.address))
69
+ byAddress.set(item.address, item.name);
70
+ }
71
+ const nextOffset = parsed.value.nextOffset;
72
+ if (!parsed.value.hasMore) {
73
+ exhaustive = true;
74
+ break;
75
+ }
76
+ if (nextOffset === null || nextOffset <= offset)
77
+ break;
78
+ offset = nextOffset;
79
+ }
80
+ const addressesByName = new Map();
81
+ for (const [address, name] of byAddress) {
82
+ const addresses = addressesByName.get(name) ?? new Set();
83
+ addresses.add(address);
84
+ addressesByName.set(name, addresses);
85
+ }
86
+ const candidates = [...byAddress.keys()];
87
+ if (exhaustive)
88
+ for (const [name, addresses] of addressesByName)
89
+ if (addresses.size === 1 && name.length > 0)
90
+ candidates.push(name);
91
+ return candidates;
92
+ };
93
+ const providerCandidates = (session) => {
94
+ if (session === undefined)
95
+ return [];
96
+ const parsed = providerStatusSchema.safeParse(session.status());
97
+ return parsed.success
98
+ ? parsed.data.providers.map(({ id }) => id).slice(0, COMPLETION_SCAN_LIMIT)
99
+ : [];
100
+ };
101
+ const evidenceCandidates = (kind, session) => {
102
+ if (session === undefined)
103
+ return [];
104
+ const candidates = [];
105
+ for (const evidence of session.exportEvidenceBundle().records) {
106
+ const remaining = COMPLETION_SCAN_LIMIT - candidates.length;
107
+ candidates.push(...evidenceValues(kind, evidence).slice(0, remaining));
108
+ if (candidates.length >= COMPLETION_SCAN_LIMIT)
109
+ break;
110
+ }
111
+ return candidates;
112
+ };
113
+ const evidenceValues = (kind, evidence) => {
114
+ if (kind === "evidence")
115
+ return [evidence.evidence_id];
116
+ if (kind === "capture")
117
+ return isProcessCaptureEvidence(evidence) ? [evidence.evidence_id] : [];
118
+ if (evidence.operation !== "inventory_artifact")
119
+ return [];
120
+ const inventory = artifactInventoryResultSchema.safeParse(evidence.normalized_result);
121
+ if (!inventory.success)
122
+ return [];
123
+ if (kind === "manifest")
124
+ return [inventory.data.manifest.manifest_id];
125
+ return inventory.data.occurrences.items.map(({ occurrence_id: id }) => id);
126
+ };
127
+ const isProcessCaptureEvidence = (evidence) => {
128
+ if (evidence.operation !== "capture_process_scenario")
129
+ return false;
130
+ const capture = processCaptureSchema.safeParse(evidence.normalized_result);
131
+ return capture.success && validateProcessCapture(capture.data).length === 0;
132
+ };
133
+ const unknownCandidates = (session) => session === undefined
134
+ ? []
135
+ : session
136
+ .listUnknowns()
137
+ .filter(({ status }) => status !== "resolved")
138
+ .map(({ unknown_id: id }) => id)
139
+ .slice(0, COMPLETION_SCAN_LIMIT);
140
+ const uniqueSorted = (values) => [
141
+ ...new Set(values.filter((value) => value.length > 0 && value.length <= MAX_COMPLETION_VALUE_LENGTH)),
142
+ ].sort(compareText);
143
+ const compareText = (left, right) => left < right ? -1 : left > right ? 1 : 0;
144
+ const normalize = (value) => value.normalize("NFKC").toLowerCase();
@@ -4,7 +4,7 @@ import { ENHANCED_TOOL_CONTRACTS, } from "../contracts/toolContracts.js";
4
4
  import { toCallToolResult } from "./toolResult.js";
5
5
  import { logToolExecution } from "./toolLogging.js";
6
6
  import { createEvidence } from "../domain/evidence.js";
7
- import { jsonValueSchema } from "../domain/jsonValue.js";
7
+ import { jsonObjectSchema } from "../domain/jsonValue.js";
8
8
  import { enhancedInputSchemas } from "../contracts/enhancedInputs.js";
9
9
  import { UnknownRegistryError } from "../domain/errors.js";
10
10
  const WORKFLOW_PROVIDER = {
@@ -32,11 +32,7 @@ const registerEnhancedTool = (server, services, contract, registration) => {
32
32
  outputSchema: contract.outputSchema,
33
33
  annotations: contract.annotations,
34
34
  }, async (input, context) => {
35
- const parsedInput = jsonValueSchema.parse(enhancedInputSchemas[name].parse(input));
36
- if (typeof parsedInput !== "object" ||
37
- parsedInput === null ||
38
- Array.isArray(parsedInput))
39
- throw new Error("Validated enhanced input was not a JSON object");
35
+ const parsedInput = jsonObjectSchema.parse(enhancedInputSchemas[name].parse(input));
40
36
  const parameters = { ...parsedInput };
41
37
  const result = await logToolExecution(registration.logger, name, () => services.execute(name, input, context.mcpReq.signal));
42
38
  if (result.ok) {
@@ -1,5 +1,5 @@
1
1
  import { createEvidence } from "../domain/evidence.js";
2
- import { jsonValueSchema } from "../domain/jsonValue.js";
2
+ import { jsonObjectSchema } from "../domain/jsonValue.js";
3
3
  import { logToolExecution } from "./toolLogging.js";
4
4
  import { toCallToolResult } from "./toolResult.js";
5
5
  import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
@@ -7,7 +7,7 @@ import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
7
7
  export const registerEvidenceTools = (server, analysis, contracts, options) => {
8
8
  for (const contract of contracts) {
9
9
  server.registerTool(contract.name, toolRegistrationOptions(contract), async (input, context) => {
10
- const parameters = jsonObject(contract.inputSchema.parse(input));
10
+ const parameters = jsonObjectSchema.parse(contract.inputSchema.parse(input));
11
11
  const execution = await logToolExecution(options.logger, contract.name, () => analysis.execute(contract.name, parameters, {
12
12
  signal: context.mcpReq.signal,
13
13
  }));
@@ -28,9 +28,3 @@ export const registerEvidenceTools = (server, analysis, contracts, options) => {
28
28
  });
29
29
  }
30
30
  };
31
- const jsonObject = (input) => {
32
- const parsed = jsonValueSchema.parse(input);
33
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
34
- throw new TypeError("Provider tool input was not an object");
35
- return parsed;
36
- };
@@ -59,7 +59,7 @@ const functionUnknownInput = ({ approved, status, leftIds, rightIds, }) => {
59
59
  recommended_probes: [
60
60
  {
61
61
  operation: "analyze_function",
62
- rationale: "Capture complete dossiers with equal providers and analysis limits.",
62
+ rationale: "Capture complete dossiers for both functions under the same target context and analysis limits.",
63
63
  },
64
64
  ],
65
65
  relationships: [],
@@ -1,7 +1,8 @@
1
+ import { runCrossVersionInvestigation } from "../application/CrossVersionInvestigation.js";
1
2
  import { buildCallPath, callPathInputSchema } from "../domain/callPath.js";
2
3
  import { correlateStaticAndRuntime, staticRuntimeCorrelationInputSchema, } from "../domain/staticRuntimeCorrelation.js";
3
4
  import { reconstructionVerificationInputSchema, verifyReconstruction, } from "../domain/reconstructionVerification.js";
4
- import { changedBehaviorInputSchema, findChangedBehavior, } from "../domain/changedBehavior.js";
5
+ import { changedBehaviorResultSchema, changedBehaviorInputSchema, findChangedBehavior, } from "../domain/changedBehavior.js";
5
6
  import { createEvidence } from "../domain/evidence.js";
6
7
  import { EvidenceIntegrityError } from "../domain/errors.js";
7
8
  import { jsonValueSchema } from "../domain/jsonValue.js";
@@ -10,15 +11,35 @@ import { CALL_PATH_PROVIDER, CHANGED_BEHAVIOR_PROVIDER, RECONSTRUCTION_PROVIDER,
10
11
  import { toCallToolResult } from "./toolResult.js";
11
12
  import { recordDerivedEvidence } from "./recordDerivedEvidence.js";
12
13
  /** Register Evidence-composed differential investigation workflows. */
13
- export const registerInvestigationTools = (server, session, contracts) => {
14
- registerChangedBehavior(server, session, contracts[0]);
14
+ export const registerInvestigationTools = (server, session, contracts, policies) => {
15
+ registerChangedBehavior(server, session, contracts[0], policies);
15
16
  registerCallPath(server, session, contracts[1]);
16
17
  registerStaticRuntime(server, session, contracts[2]);
17
18
  registerReconstruction(server, session, contracts[3]);
18
19
  };
19
- const registerChangedBehavior = (server, session, contract) => {
20
- server.registerTool(contract.name, contractOptions(contract), (input) => {
20
+ const investigationExecution = (session, signal, inputRoots) => ({ session, signal, inputRoots });
21
+ const isIncomplete = (status) => status === "unknown" || status === "truncated";
22
+ const registerChangedBehavior = (server, session, contract, policies) => {
23
+ server.registerTool(contract.name, contractOptions(contract), async (input, context) => {
21
24
  const parsed = changedBehaviorInputSchema.parse(input);
25
+ if (parsed.investigation_run !== undefined) {
26
+ const investigated = await runCrossVersionInvestigation(parsed.investigation_run, policies.evidenceFiles, investigationExecution(session, context.mcpReq.signal, policies.inputRoots));
27
+ if (!investigated.ok)
28
+ return toCallToolResult(investigated, contract);
29
+ const result = changedBehaviorResultSchema.parse(investigated.value.evidence.normalized_result);
30
+ return toCallToolResult(recordWorkflowEvidence(session, investigated.value.evidence, parsed.unknown_registry_approved, isIncomplete(result.behavior_status), {
31
+ question: `Automatic changed behavior run ${result.investigation_run?.run_id ?? "unknown"} remains ${result.behavior_status}`,
32
+ domain: "changed-behavior",
33
+ requiredAuthority: "controlled-replay",
34
+ requiredConfidence: "observed",
35
+ probes: [
36
+ {
37
+ operation: "capture_process_scenario",
38
+ rationale: "Capture both versions under the same bounded scenario and environment.",
39
+ },
40
+ ],
41
+ }), contract);
42
+ }
22
43
  const closure = evidenceClosure(session, comparisonClosure(parsed.comparisons));
23
44
  if (!closure.ok)
24
45
  return toCallToolResult(closure, contract);
@@ -38,9 +59,8 @@ const registerChangedBehavior = (server, session, contract) => {
38
59
  limitations: result.limitations,
39
60
  evidenceLinks: links,
40
61
  });
41
- const recorded = recordWorkflowEvidence(session, evidence, parsed.unknown_registry_approved, result.behavior_status === "unknown" ||
42
- result.behavior_status === "truncated", {
43
- question: `Changed behavior result ${evidence.evidence_id} remains ${result.behavior_status}`,
62
+ const recorded = recordWorkflowEvidence(session, evidence, parsed.unknown_registry_approved, isIncomplete(result.behavior_status), {
63
+ question: "Did both versions behave the same under a complete controlled replay?",
44
64
  domain: "changed-behavior",
45
65
  requiredAuthority: "controlled-replay",
46
66
  requiredConfidence: "observed",
@@ -80,7 +100,7 @@ const registerCallPath = (server, session, contract) => {
80
100
  evidenceLinks: links,
81
101
  });
82
102
  const recorded = recordWorkflowEvidence(session, evidence, parsed.unknown_registry_approved, result.status === "unknown" || result.status === "truncated", {
83
- question: `Call path ${parsed.start.address} → ${parsed.goal.address} is ${result.status}`,
103
+ question: "Can the requested call path be established from complete analysis?",
84
104
  domain: "call-path",
85
105
  requiredAuthority: "shipped-artifact",
86
106
  requiredConfidence: "derived",
@@ -120,7 +140,7 @@ const registerStaticRuntime = (server, session, contract) => {
120
140
  evidenceLinks: links,
121
141
  });
122
142
  return toCallToolResult(recordWorkflowEvidence(session, evidence, parsed.unknown_registry_approved, result.status === "unknown" || result.status === "truncated", {
123
- question: `Static/runtime correlation result ${evidence.evidence_id} is ${result.status}`,
143
+ question: "Does runtime behavior match the available static analysis?",
124
144
  domain: "static-runtime-correlation",
125
145
  requiredAuthority: null,
126
146
  requiredConfidence: "derived",
@@ -163,7 +183,7 @@ const registerReconstruction = (server, session, contract) => {
163
183
  evidenceLinks: links,
164
184
  });
165
185
  return toCallToolResult(recordWorkflowEvidence(session, evidence, parsed.unknown_registry_approved, result.status === "unknown", {
166
- question: `Reconstruction ${result.specification_sha256} has unknown claims`,
186
+ question: "Does the reconstruction satisfy every declared claim?",
167
187
  domain: "reconstruction-verification",
168
188
  requiredAuthority: null,
169
189
  requiredConfidence: "derived",
@@ -1,5 +1,5 @@
1
1
  import { OFFICIAL_TOOL_CONTRACTS } from "../contracts/toolContracts.js";
2
- import { jsonValueSchema } from "../domain/jsonValue.js";
2
+ import { jsonObjectSchema, jsonValueSchema, } from "../domain/jsonValue.js";
3
3
  import { toCallToolResult } from "./toolResult.js";
4
4
  import { logToolExecution } from "./toolLogging.js";
5
5
  import { createEvidence } from "../domain/evidence.js";
@@ -45,9 +45,9 @@ const registerOfficialTool = (server, analysis, contract, registration) => {
45
45
  registration.recordUnknown !== undefined) {
46
46
  const unknown = registration.recordUnknown({
47
47
  approved: true,
48
- question: `${contract.name} is unavailable: ${result.error.reason}`,
48
+ question: "The requested analysis is unavailable for the current target.",
49
49
  severity: "medium",
50
- domain: "provider-capability",
50
+ domain: "analysis-capability",
51
51
  supporting_evidence_ids: [],
52
52
  contradicting_evidence_ids: [],
53
53
  required_authority: "shipped-artifact",
@@ -56,7 +56,7 @@ const registerOfficialTool = (server, analysis, contract, registration) => {
56
56
  recommended_probes: [
57
57
  {
58
58
  operation: contract.name,
59
- rationale: "Use a provider that declares this capability available.",
59
+ rationale: "Choose another analysis or target that can answer this question.",
60
60
  },
61
61
  ],
62
62
  relationships: [],
@@ -70,19 +70,12 @@ const registerOfficialTool = (server, analysis, contract, registration) => {
70
70
  });
71
71
  };
72
72
  const projectOfficialArguments = (contract, input) => {
73
- const parsed = jsonValueSchema.safeParse(input);
74
- if (!parsed.success ||
75
- typeof parsed.data !== "object" ||
76
- parsed.data === null ||
77
- Array.isArray(parsed.data)) {
78
- // The SDK validates this with the same schema before invoking the callback.
79
- throw new Error("Validated MCP tool input was not a JSON object");
80
- }
73
+ const parsed = jsonObjectSchema.parse(contract.inputSchema.parse(input));
81
74
  const projected = {};
82
75
  for (const key of Object.keys(contract.inputSchema.shape)) {
83
76
  if (key === "unknown_registry_approved")
84
77
  continue;
85
- projected[key] = parsed.data[key] ?? null;
78
+ projected[key] = jsonValueSchema.parse(parsed[key] ?? null);
86
79
  }
87
80
  return projected;
88
81
  };
@@ -80,17 +80,11 @@ const validateCaptureSources = (session, parsed) => {
80
80
  evidence.confidence !== "observed" ||
81
81
  evidence.authority !== "controlled-replay" ||
82
82
  evidenceCapture === undefined ||
83
- canonicalJson(evidenceCapture) !== canonicalJson(capture))
83
+ canonicalize(evidenceCapture) !== canonicalize(capture))
84
84
  return err(new EvidenceIntegrityError("Process comparison payload does not match its source Evidence"));
85
85
  }
86
86
  return { ok: true, value: null };
87
87
  };
88
- const canonicalJson = (value) => {
89
- const encoded = canonicalize(value);
90
- if (encoded === undefined)
91
- throw new TypeError("Process capture could not be canonicalized");
92
- return encoded;
93
- };
94
88
  const comparisonUnknownInput = (parsed, comparison) => {
95
89
  if (parsed.unknown_registry_approved !== true ||
96
90
  comparison.status === "unchanged")
@@ -0,0 +1,67 @@
1
+ import { completable, } from "@modelcontextprotocol/server";
2
+ import { z } from "zod";
3
+ import { PROMPT_CONTRACTS, renderGuidedPrompt, } from "../contracts/promptContracts.js";
4
+ import { createPromptCompletionSource, } from "./promptCompletion.js";
5
+ const promptValuesSchema = z.record(z.string(), z.string());
6
+ /** Register every guided workflow and its session-scoped argument completers. */
7
+ export const registerGuidedPrompts = (server, analysis, session) => {
8
+ const completion = createPromptCompletionSource(analysis, session);
9
+ const prompts = new Map();
10
+ for (const contract of PROMPT_CONTRACTS)
11
+ prompts.set(contract.name, registerPrompt(server, contract, completion));
12
+ return {
13
+ update(contract) {
14
+ const prompt = prompts.get(contract.name);
15
+ if (prompt === undefined)
16
+ throw new RangeError(`Unknown guided prompt ${contract.name}`);
17
+ const argsSchema = promptArgumentsSchema(contract, completion);
18
+ prompt.update({
19
+ title: contract.title,
20
+ description: contract.description,
21
+ argsSchema,
22
+ callback: (arguments_) => promptResult(contract, arguments_),
23
+ });
24
+ },
25
+ };
26
+ };
27
+ const registerPrompt = (server, contract, completion) => {
28
+ const argsSchema = promptArgumentsSchema(contract, completion);
29
+ return server.registerPrompt(contract.name, {
30
+ title: contract.title,
31
+ description: contract.description,
32
+ argsSchema,
33
+ }, (arguments_) => promptResult(contract, arguments_));
34
+ };
35
+ const promptArgumentsSchema = (contract, completion) => {
36
+ const shape = {};
37
+ for (const [name, argument] of Object.entries(contract.arguments))
38
+ shape[name] = argumentSchema(argument, completion);
39
+ return z.object(shape);
40
+ };
41
+ const argumentSchema = (argument, completion) => {
42
+ const value = z
43
+ .string()
44
+ .trim()
45
+ .min(1)
46
+ .max(4_096)
47
+ .describe(argument.description);
48
+ const kind = argument.completion;
49
+ const completed = kind === undefined
50
+ ? value
51
+ : completable(value, async (partial, context) => [
52
+ ...(await completion.complete(kind, partial, context)),
53
+ ]);
54
+ return argument.required ? completed : completed.optional();
55
+ };
56
+ const promptResult = (contract, arguments_) => ({
57
+ description: contract.description,
58
+ messages: [
59
+ {
60
+ role: "user",
61
+ content: {
62
+ type: "text",
63
+ text: renderGuidedPrompt(contract, promptValuesSchema.parse(arguments_)),
64
+ },
65
+ },
66
+ ],
67
+ });
@@ -0,0 +1,11 @@
1
+ import { SESSION_TOOL_CONTRACTS } from "../contracts/toolContracts.js";
2
+ import { toCallToolResult } from "./toolResult.js";
3
+ /** Register the read-only provider and target status operation. */
4
+ export const registerSessionStatusTool = (server, session, contract) => {
5
+ server.registerTool(contract.name, {
6
+ description: contract.description,
7
+ inputSchema: contract.inputSchema,
8
+ outputSchema: contract.outputSchema,
9
+ annotations: contract.annotations,
10
+ }, () => toCallToolResult({ ok: true, value: session.status() }, contract));
11
+ };
@@ -7,6 +7,7 @@ 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 { readAnalysisSnapshot, writeAnalysisSnapshot, } from "../application/AnalysisSnapshotFiles.js";
10
11
  import { 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";
@@ -15,13 +16,15 @@ 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";
18
21
  const recordProcessResidualUnknowns = (session, scenario, evidence, residuals) => {
19
22
  if (scenario.unknown_registry_approved !== true)
20
23
  return ok(null);
21
24
  for (const residual of residuals) {
22
25
  const unknown = session.recordUnknown({
23
26
  approved: true,
24
- question: `${residual.scope}: ${residual.reason}`,
27
+ question: `Was ${residual.scope} behavior fully observed during capture?`,
25
28
  severity: "medium",
26
29
  domain: `process-${residual.scope}`,
27
30
  supporting_evidence_ids: [evidence.evidence_id],
@@ -163,7 +166,7 @@ const registerUnknownTools = ({ server, session, contracts: [listContract, recor
163
166
  return toCallToolResult(session.verifyUnknownResolution(unknownId), verifyContract);
164
167
  });
165
168
  };
166
- const registerLifecycleTools = (server, session, logger, contracts) => {
169
+ const registerLifecycleTools = ({ server, session, logger, contracts, snapshotFilePolicy, }) => {
167
170
  const [openContract, closeContract, statusContract] = contracts;
168
171
  server.registerTool(openContract.name, {
169
172
  description: openContract.description,
@@ -171,8 +174,18 @@ const registerLifecycleTools = (server, session, logger, contracts) => {
171
174
  outputSchema: openContract.outputSchema,
172
175
  annotations: openContract.annotations,
173
176
  }, 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 }));
177
+ const parsed = openBinaryInputSchema.parse(input);
178
+ let snapshot;
179
+ if (parsed.snapshot_path !== undefined) {
180
+ const loaded = await readAnalysisSnapshot(parsed.snapshot_path, snapshotFilePolicy);
181
+ if (!loaded.ok)
182
+ return toCallToolResult(loaded, openContract);
183
+ snapshot = loaded.value;
184
+ }
185
+ const opened = await logToolExecution(logger, openContract.name, () => session.open(parsed.path, {
186
+ signal: context.mcpReq.signal,
187
+ ...(snapshot === undefined ? {} : { snapshot }),
188
+ }));
176
189
  return opened.ok
177
190
  ? toCallToolResult({
178
191
  ok: true,
@@ -192,23 +205,40 @@ const registerLifecycleTools = (server, session, logger, contracts) => {
192
205
  inputSchema: closeContract.inputSchema,
193
206
  outputSchema: closeContract.outputSchema,
194
207
  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));
208
+ }, async (input) => {
209
+ const parsed = closeBinaryInputSchema.parse(input);
210
+ if (parsed.snapshot_path !== undefined) {
211
+ const snapshot = session.exportAnalysisSnapshot();
212
+ if (!snapshot.ok)
213
+ return toCallToolResult(snapshot, closeContract);
214
+ const written = await writeAnalysisSnapshot(snapshot.value, parsed.snapshot_path, parsed.overwrite, snapshotFilePolicy);
215
+ if (!written.ok)
216
+ return toCallToolResult(written, closeContract);
217
+ const closed = await logToolExecution(logger, closeContract.name, () => session.close());
218
+ return closed.ok
219
+ ? toCallToolResult(ok({
220
+ path: written.value.path,
221
+ bytes: written.value.bytes,
222
+ entries: snapshot.value.entries.length,
223
+ }), closeContract)
224
+ : toCallToolResult(closed, closeContract);
225
+ }
226
+ return toCallToolResult(await logToolExecution(logger, closeContract.name, () => session.close()), closeContract);
227
+ });
228
+ registerSessionStatusTool(server, session, statusContract);
202
229
  };
203
230
  export const registerSessionTools = (server, session, logger, options = {}) => {
204
231
  const processPolicy = options.processPolicy ?? DENY_PROCESS_POLICY;
205
232
  const evidenceFilePolicy = options.evidenceFilePolicy ?? DENY_EVIDENCE_FILE_POLICY;
233
+ const analysisSnapshotFilePolicy = options.analysisSnapshotFilePolicy ?? DENY_EVIDENCE_FILE_POLICY;
206
234
  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
- ]);
235
+ registerLifecycleTools({
236
+ server,
237
+ session,
238
+ logger,
239
+ contracts: [openContract, closeContract, statusContract],
240
+ snapshotFilePolicy: analysisSnapshotFilePolicy,
241
+ });
212
242
  registerEvidenceTools({
213
243
  server,
214
244
  session,
@@ -227,12 +257,16 @@ export const registerSessionTools = (server, session, logger, options = {}) => {
227
257
  registerArtifactComparisonTool(server, session, compareArtifactsContract);
228
258
  registerFunctionComparisonTool(server, session, compareFunctionsContract);
229
259
  registerBundleComparisonTool(server, session, compareBundlesContract);
230
- registerInvestigationTools(server, session, [
260
+ const investigationContracts = [
231
261
  changedBehaviorContract,
232
262
  callPathContract,
233
263
  staticRuntimeContract,
234
264
  reconstructionContract,
235
- ]);
265
+ ];
266
+ registerInvestigationTools(server, session, investigationContracts, {
267
+ evidenceFiles: evidenceFilePolicy,
268
+ inputRoots: options.investigationInputRoots ?? [],
269
+ });
236
270
  registerUnknownTools({
237
271
  server,
238
272
  session,
@@ -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";