rea-agents 2.5.0 → 3.0.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 (88) hide show
  1. package/README.md +14 -4
  2. package/bridge/ghidra/ReaGhidraBridge.java +477 -4
  3. package/bridge/hopper_bridge.py +54 -4
  4. package/dist/application/AnalysisContextQueries.js +130 -0
  5. package/dist/application/AnalysisSnapshotCache.js +4 -2
  6. package/dist/application/ArtifactInventory/scanCanonical.js +1 -1
  7. package/dist/application/BinarySession.js +5 -4
  8. package/dist/application/BinarySessionComposition.js +21 -0
  9. package/dist/application/BinarySessionRecords.js +79 -9
  10. package/dist/application/CapabilityInventory.js +16 -2
  11. package/dist/application/ClientRegistrationStatus.js +4 -1
  12. package/dist/application/DirectAnalysis.js +6 -2
  13. package/dist/application/EnhancedTools.js +37 -7
  14. package/dist/application/EvidenceLedger.js +45 -16
  15. package/dist/application/JavaScriptArtifactAnalysis.js +13 -4
  16. package/dist/application/LazyAnalysisProvider.js +77 -0
  17. package/dist/application/LoopbackReplay.js +1 -1
  18. package/dist/application/NativeApiInspection.js +80 -0
  19. package/dist/application/SessionProviderRouter.js +6 -6
  20. package/dist/application/SetupClientConfiguration.js +4 -2
  21. package/dist/application/runtime.js +26 -9
  22. package/dist/browser/PlaywrightBrowserScenarioProvider.js +8 -2
  23. package/dist/catalogIdentity.js +3 -3
  24. package/dist/cli/artifactCommands.js +5 -3
  25. package/dist/cli/coreAnalysisCommands.js +41 -0
  26. package/dist/cliCommandNames.js +1 -0
  27. package/dist/contracts/artifactToolContracts.js +8 -3
  28. package/dist/contracts/enhancedInputs.js +10 -0
  29. package/dist/contracts/functionWorkflowToolContracts.js +30 -0
  30. package/dist/contracts/promptContracts.js +14 -15
  31. package/dist/contracts/toolContractExamples.js +3 -0
  32. package/dist/contracts/toolContracts.js +21 -4
  33. package/dist/contracts/toolEffects.js +4 -0
  34. package/dist/contracts/toolOutputSchemaGroups.js +50 -10
  35. package/dist/domain/analysisErrorPresentation.js +8 -2
  36. package/dist/domain/analysisErrorProjection.js +1 -0
  37. package/dist/domain/bytecodeProvider.js +179 -0
  38. package/dist/domain/conformancePackage.js +169 -0
  39. package/dist/domain/conformanceReplay.js +104 -0
  40. package/dist/domain/conformanceTrustGate.js +193 -0
  41. package/dist/domain/customProtocolCapture.js +137 -0
  42. package/dist/domain/eventProcessTree.js +214 -0
  43. package/dist/domain/evidenceBundle.js +47 -0
  44. package/dist/domain/hopperStartupFailure.js +1 -1
  45. package/dist/domain/hopperValues.js +4 -1
  46. package/dist/domain/inputIssueProjection.js +7 -1
  47. package/dist/domain/javascriptSemanticAnalysis.js +8 -12
  48. package/dist/domain/javascriptSourceParser.js +15 -0
  49. package/dist/domain/javascriptStaticAnalysis.js +8 -12
  50. package/dist/domain/mobileApplicationInvestigation.js +159 -0
  51. package/dist/domain/nativeApiBoundary.js +130 -0
  52. package/dist/domain/objcSwiftMetadata.js +150 -0
  53. package/dist/domain/packageAnalysis.js +123 -0
  54. package/dist/domain/peInspection.js +196 -0
  55. package/dist/domain/protocolCapture.js +243 -0
  56. package/dist/dotnet/ManagedStaticProvider.js +34 -5
  57. package/dist/generatedMcpToolCatalog.js +37 -0
  58. package/dist/generatedPackageMetadata.js +4 -4
  59. package/dist/ghidra/GhidraDefaults.js +1 -1
  60. package/dist/ghidra/GhidraFunctionValues.js +27 -1
  61. package/dist/ghidra/GhidraProviderCapabilities.js +40 -38
  62. package/dist/hopper/HopperClient.js +26 -2
  63. package/dist/hopper/HopperProvider.js +42 -30
  64. package/dist/hopper/HopperRequestQueue.js +32 -4
  65. package/dist/hopper/HopperResponseStream.js +2 -2
  66. package/dist/hopper/protocol.js +63 -21
  67. package/dist/main/transport.js +2 -5
  68. package/dist/main.js +14 -12
  69. package/dist/mcpDoctor.js +2 -2
  70. package/dist/mcpStartupPolicy.js +14 -0
  71. package/dist/server/LazyToolCatalog.js +134 -0
  72. package/dist/server/createServer.js +64 -171
  73. package/dist/server/hydrateServerTools.js +191 -0
  74. package/dist/server/registerEnhancedTools.js +13 -3
  75. package/dist/server/registerEvidenceResources.js +50 -101
  76. package/dist/server/registerInvestigationTools.js +1 -2
  77. package/dist/server/registerJavaScriptApplicationGraphResource.js +0 -4
  78. package/dist/server/registerReconstructionObligationLedgerResource.js +0 -7
  79. package/dist/server/registerReconstructionReadinessResource.js +0 -7
  80. package/dist/server/registerSessionRecordTools.js +82 -12
  81. package/dist/server/registerSessionStatusTool.js +1 -1
  82. package/dist/server/registerSessionTools.js +21 -1
  83. package/dist/server/toolRegistrationOptions.js +21 -6
  84. package/dist/server/toolResult.js +12 -0
  85. package/package.json +21 -10
  86. package/scripts/package-runner-bootstrap.mjs +72 -0
  87. package/scripts/rea.mjs +11 -1
  88. package/skills/reverse-engineer-anything/SKILL.md +10 -3
@@ -4,13 +4,12 @@ import canonicalize from "canonicalize";
4
4
  import { registerJavaScriptApplicationGraphResource } from "./registerJavaScriptApplicationGraphResource.js";
5
5
  import { registerReconstructionObligationLedgerResource } from "./registerReconstructionObligationLedgerResource.js";
6
6
  import { registerReconstructionReadinessResource } from "./registerReconstructionReadinessResource.js";
7
- const evidenceUri = (evidenceId) => `rea://evidence/${evidenceId}`;
8
7
  /** Expose immutable session-owned Evidence v2 records as MCP resources. */
9
8
  export const registerEvidenceResources = (server, session) => {
10
9
  registerReconstructionCoverageResource(server, session);
11
10
  registerInvestigationWorkspaceResource(server, session);
12
11
  registerSessionEvidenceResource(server, session);
13
- registerActiveResidualUnknownsResource(server, session);
12
+ registerEvidenceBundleResource(server, session);
14
13
  registerAnalysisSnapshotResource(server, session);
15
14
  registerArtifactPageResource(server, session);
16
15
  registerFunctionDossierResource(server, session);
@@ -19,20 +18,15 @@ export const registerEvidenceResources = (server, session) => {
19
18
  registerReconstructionReadinessResource(server, session);
20
19
  registerJavaScriptApplicationGraphResource(server, session);
21
20
  registerResidualUnknownResource(server, session);
21
+ session.onAnalysisSnapshotChanged?.(() => {
22
+ void server.server
23
+ .sendResourceUpdated({ uri: "rea://snapshot/current" })
24
+ .catch(() => undefined);
25
+ });
22
26
  };
23
27
  const registerReconstructionCoverageResource = (server, session) => {
24
28
  server.registerResource("reconstruction-coverage-revision", new ResourceTemplate("rea://reconstruction-coverage/{workspaceId}/revision/{revision}", {
25
- list: () => ({
26
- resources: session
27
- .reconstructionCoverageWorkspaces()
28
- .map((workspace) => ({
29
- uri: coverageWorkspaceUri(workspace.workspace_id, workspace.revision),
30
- name: `${workspace.workspace_id} revision ${String(workspace.revision)}`,
31
- title: `${workspace.name} coverage revision ${String(workspace.revision)}`,
32
- description: "Immutable evidence-backed reconstruction coverage workspace revision.",
33
- mimeType: "application/json",
34
- })),
35
- }),
29
+ list: undefined,
36
30
  }), {
37
31
  title: "Reconstruction coverage revision",
38
32
  description: "Canonical CAS-linked surfaces, owners, claims, verifiers, evidence, and completion boundaries.",
@@ -50,27 +44,7 @@ const registerReconstructionCoverageResource = (server, session) => {
50
44
  };
51
45
  const registerInvestigationWorkspaceResource = (server, session) => {
52
46
  server.registerResource("investigation-workspace-revision", new ResourceTemplate("rea://workspace/{workspaceId}/revision/{revision}", {
53
- list: () => ({
54
- resources: session.investigationWorkspaces().map((workspace) => ({
55
- uri: workspaceUri(workspace.workspace_id, workspace.revision),
56
- name: `${workspace.workspace_id} revision ${String(workspace.revision)}`,
57
- title: `${workspace.name} revision ${String(workspace.revision)}`,
58
- description: "Immutable CAS-linked investigation workspace revision.",
59
- mimeType: "application/json",
60
- })),
61
- }),
62
- complete: {
63
- workspaceId: (prefix) => [
64
- ...new Set(session
65
- .investigationWorkspaces()
66
- .map(({ workspace_id }) => workspace_id)),
67
- ].filter((workspaceId) => workspaceId.startsWith(prefix)),
68
- revision: (prefix, context) => session
69
- .investigationWorkspaces()
70
- .filter(({ workspace_id }) => context?.arguments?.workspaceId === workspace_id)
71
- .map(({ revision }) => String(revision))
72
- .filter((revision) => revision.startsWith(prefix)),
73
- },
47
+ list: undefined,
74
48
  }), {
75
49
  title: "Investigation workspace revision",
76
50
  description: "Immutable session-retained workspace with CAS linkage.",
@@ -88,21 +62,7 @@ const registerInvestigationWorkspaceResource = (server, session) => {
88
62
  };
89
63
  const registerSessionEvidenceResource = (server, session) => {
90
64
  server.registerResource("session-evidence", new ResourceTemplate("rea://evidence/{evidenceId}", {
91
- list: () => ({
92
- resources: session.exportEvidenceBundle().records.map((evidence) => ({
93
- uri: evidenceUri(evidence.evidence_id),
94
- name: evidence.evidence_id,
95
- title: `${evidence.operation} evidence`,
96
- description: `Session-owned Evidence v2 record for ${evidence.operation}.`,
97
- mimeType: "application/json",
98
- })),
99
- }),
100
- complete: {
101
- evidenceId: (prefix) => session
102
- .exportEvidenceBundle()
103
- .records.map(({ evidence_id }) => evidence_id)
104
- .filter((evidenceId) => evidenceId.startsWith(prefix)),
105
- },
65
+ list: undefined,
106
66
  }), {
107
67
  title: "Session evidence",
108
68
  description: "Immutable Evidence v2 records owned by this REA session.",
@@ -125,42 +85,52 @@ const registerSessionEvidenceResource = (server, session) => {
125
85
  };
126
86
  });
127
87
  };
128
- const registerActiveResidualUnknownsResource = (server, session) => {
129
- server.registerResource("active-residual-unknowns", "rea://unknowns/active", {
130
- title: "Active residual unknowns",
131
- description: "Current unresolved session-owned residual unknown heads.",
88
+ const registerEvidenceBundleResource = (server, session) => {
89
+ server.registerResource("evidence-bundle", new ResourceTemplate("rea://evidence-bundle/{bundleDigest}", {
90
+ list: undefined,
91
+ }), {
92
+ title: "Evidence bundle",
93
+ description: "Immutable session-retained canonical Evidence v2 bundle.",
132
94
  mimeType: "application/json",
133
- }, (uri) => jsonResource(uri.href, session.listUnknowns().filter(({ status }) => status !== "resolved")));
95
+ }, (uri, variables) => {
96
+ const digest = stringVariable(variables.bundleDigest, uri.href);
97
+ if (!/^[a-f0-9]{64}$/u.test(digest))
98
+ throw new ResourceNotFoundError(uri.href);
99
+ const text = session.retainedEvidenceBundle(digest);
100
+ if (text === undefined)
101
+ throw new ResourceNotFoundError(uri.href);
102
+ return {
103
+ contents: [
104
+ {
105
+ uri: uri.href,
106
+ mimeType: "application/json",
107
+ text,
108
+ },
109
+ ],
110
+ };
111
+ });
134
112
  };
135
113
  const registerAnalysisSnapshotResource = (server, session) => {
136
- server.registerResource("analysis-snapshot", new ResourceTemplate("rea://snapshot/{snapshotDigest}", {
137
- list: () => {
138
- const snapshot = session.exportAnalysisSnapshot();
139
- if (!snapshot.ok)
140
- return { resources: [] };
141
- const digest = contentDigest(snapshot.value);
142
- return {
143
- resources: [
144
- {
145
- uri: `rea://snapshot/${digest}`,
146
- name: digest,
147
- title: "Current analysis snapshot",
148
- description: "Immutable projection of the current target cache.",
149
- mimeType: "application/json",
150
- },
151
- ],
152
- };
153
- },
154
- }), {
114
+ server.registerResource("analysis-snapshot", "rea://snapshot/current", {
155
115
  title: "Analysis snapshot",
156
- description: "Current provider-neutral analysis snapshot by content digest.",
116
+ description: "Mutable provider-neutral analysis snapshot for the current native target.",
157
117
  mimeType: "application/json",
158
- }, (uri, variables) => {
159
- const requested = stringVariable(variables.snapshotDigest, uri.href);
118
+ }, (uri) => {
160
119
  const snapshot = session.exportAnalysisSnapshot();
161
- if (!snapshot.ok || contentDigest(snapshot.value) !== requested)
162
- throw new ResourceNotFoundError(uri.href);
163
- return jsonResource(uri.href, snapshot.value);
120
+ if (!snapshot.ok)
121
+ return jsonResource(uri.href, {
122
+ state: "unavailable",
123
+ reason: snapshot.error.message,
124
+ remediation: "Open a native target with a concrete analysis profile. After metadata mutation, call open_binary again for the current target to begin a fresh cache.",
125
+ });
126
+ return jsonResource(uri.href, {
127
+ state: "available",
128
+ target: snapshot.value.target,
129
+ profile: snapshot.value.binding,
130
+ content_digest: contentDigest(snapshot.value),
131
+ entry_count: snapshot.value.entries.length,
132
+ snapshot: snapshot.value,
133
+ });
164
134
  });
165
135
  };
166
136
  const registerArtifactPageResource = (server, session) => {
@@ -221,7 +191,6 @@ const registerEvidenceSectionResource = (server, session) => {
221
191
  server.registerResource("evidence-section", new ResourceTemplate("rea://evidence/{evidenceId}/section/{section}", {
222
192
  list: undefined,
223
193
  complete: {
224
- evidenceId: (prefix) => evidenceIds(session, prefix),
225
194
  section: (prefix) => EVIDENCE_SECTIONS.filter((section) => section.startsWith(prefix)),
226
195
  },
227
196
  }), {
@@ -251,21 +220,7 @@ const registerEvidenceSectionResource = (server, session) => {
251
220
  };
252
221
  const registerResidualUnknownResource = (server, session) => {
253
222
  server.registerResource("residual-unknown", new ResourceTemplate("rea://unknown/{unknownId}", {
254
- list: () => ({
255
- resources: session.listUnknowns().map((unknown) => ({
256
- uri: `rea://unknown/${unknown.unknown_id}`,
257
- name: unknown.unknown_id,
258
- title: unknown.question,
259
- description: `Current ${unknown.status} residual unknown revision.`,
260
- mimeType: "application/json",
261
- })),
262
- }),
263
- complete: {
264
- unknownId: (prefix) => session
265
- .listUnknowns()
266
- .map(({ unknown_id }) => unknown_id)
267
- .filter((unknownId) => unknownId.startsWith(prefix)),
268
- },
223
+ list: undefined,
269
224
  }), {
270
225
  title: "Residual unknown",
271
226
  description: "Current session-owned residual unknown head.",
@@ -283,8 +238,6 @@ const registerResidualUnknownResource = (server, session) => {
283
238
  return jsonResource(uri.href, { current: unknown, history });
284
239
  });
285
240
  };
286
- const coverageWorkspaceUri = (workspaceId, revision) => `rea://reconstruction-coverage/${workspaceId}/revision/${String(revision)}`;
287
- const workspaceUri = (workspaceId, revision) => `rea://workspace/${workspaceId}/revision/${String(revision)}`;
288
241
  const EVIDENCE_SECTIONS = [
289
242
  "result",
290
243
  "terminal",
@@ -295,10 +248,6 @@ const EVIDENCE_SECTIONS = [
295
248
  "occurrences",
296
249
  "edges",
297
250
  ];
298
- const evidenceIds = (session, prefix) => session
299
- .exportEvidenceBundle()
300
- .records.map(({ evidence_id }) => evidence_id)
301
- .filter((evidenceId) => evidenceId.startsWith(prefix));
302
251
  const stringVariable = (value, uri) => {
303
252
  if (typeof value !== "string")
304
253
  throw new ResourceNotFoundError(uri);
@@ -114,8 +114,7 @@ const runAutomaticInvestigation = async (input) => {
114
114
  if (!investigated.ok)
115
115
  return toCallToolResult(investigated, contract);
116
116
  const workspace = investigated.value.workspace;
117
- if (session.retainInvestigationWorkspace(workspace) === "added")
118
- input.server.sendResourceListChanged();
117
+ session.retainInvestigationWorkspace(workspace);
119
118
  const result = changedBehaviorResultSchema.parse(investigated.value.evidence.normalized_result);
120
119
  const toolResult = toCallToolResult(recordWorkflowEvidence(session, investigated.value.evidence, input.unknownRegistryApproved, isIncomplete(result.behavior_status), {
121
120
  question: `Automatic changed behavior run ${result.investigation_run?.run_id ?? "unknown"} remains ${result.behavior_status}`,
@@ -5,10 +5,6 @@ export const registerJavaScriptApplicationGraphResource = (server, session) => {
5
5
  server.registerResource("javascript-application-graph-page", new ResourceTemplate("rea://evidence/{evidenceId}/application-graph/{collection}/offset/{offset}/limit/{limit}", {
6
6
  list: undefined,
7
7
  complete: {
8
- evidenceId: (prefix) => session
9
- .exportEvidenceBundle()
10
- .records.map(({ evidence_id }) => evidence_id)
11
- .filter((evidenceId) => evidenceId.startsWith(prefix)),
12
8
  collection: (prefix) => ["nodes", "edges"].filter((name) => name.startsWith(prefix)),
13
9
  },
14
10
  }), {
@@ -4,13 +4,6 @@ import { reconstructionObligationLedgerPageSchema } from "../domain/reconstructi
4
4
  export const registerReconstructionObligationLedgerResource = (server, session) => {
5
5
  server.registerResource("reconstruction-obligation-ledger-page", new ResourceTemplate("rea://evidence/{evidenceId}/reconstruction-obligation-ledger", {
6
6
  list: undefined,
7
- complete: {
8
- evidenceId: (prefix) => session
9
- .exportEvidenceBundle()
10
- .records.filter(({ predicate_type: predicateType }) => predicateType === "rea.reconstruction-obligation-ledger/v1")
11
- .map(({ evidence_id: evidenceId }) => evidenceId)
12
- .filter((evidenceId) => evidenceId.startsWith(prefix)),
13
- },
14
7
  }), {
15
8
  title: "Reconstruction obligation ledger page",
16
9
  description: "One deterministic Evidence v2-backed obligation page with full-ledger closure digest and summaries.",
@@ -4,13 +4,6 @@ import { reconstructionReadinessReportSchema } from "../domain/reconstructionRea
4
4
  export const registerReconstructionReadinessResource = (server, session) => {
5
5
  server.registerResource("reconstruction-readiness-report", new ResourceTemplate("rea://evidence/{evidenceId}/reconstruction-readiness-report", {
6
6
  list: undefined,
7
- complete: {
8
- evidenceId: (prefix) => session
9
- .exportEvidenceBundle()
10
- .records.filter(({ predicate_type: predicateType }) => predicateType === "rea.reconstruction-readiness-report/v1")
11
- .map(({ evidence_id: evidenceId }) => evidenceId)
12
- .filter((evidenceId) => evidenceId.startsWith(prefix)),
13
- },
14
7
  }), {
15
8
  title: "Reconstruction readiness report",
16
9
  description: "Full deterministic nine-stage conformance report and replay identity.",
@@ -1,5 +1,5 @@
1
1
  import { readEvidenceBundle, writeEvidenceBundle, } from "../application/EvidenceBundleFiles.js";
2
- import { exportEvidenceBundleInputSchema, importEvidenceBundleInputSchema, listUnknownsInputSchema, SESSION_TOOL_CONTRACTS, verifyUnknownResolutionInputSchema, } from "../contracts/toolContracts.js";
2
+ import { exportEvidenceBundleInputSchema, importEvidenceBundleInputSchema, listUnknownsInputSchema, snapshotEvidenceBundleInputSchema, SESSION_TOOL_CONTRACTS, verifyUnknownResolutionInputSchema, } from "../contracts/toolContracts.js";
3
3
  import { AnalysisProtocolError, PermissionRequiredError, } from "../domain/errors.js";
4
4
  import { recordUnknownInputSchema, updateUnknownInputSchema, } from "../domain/residualUnknown.js";
5
5
  import { err, ok } from "../domain/result.js";
@@ -10,6 +10,7 @@ import { toCallToolResult } from "./toolResult.js";
10
10
  export const registerEvidenceTools = (registration) => {
11
11
  registerExportEvidenceTool(registration);
12
12
  registerImportEvidenceTool(registration);
13
+ registerSnapshotEvidenceTool(registration);
13
14
  };
14
15
  const registerExportEvidenceTool = ({ server, session, exportContract, filePolicy, permissionAuthority, }) => {
15
16
  server.registerTool(exportContract.name, toolRegistrationOptions(exportContract), async (input) => {
@@ -18,8 +19,6 @@ const registerExportEvidenceTool = ({ server, session, exportContract, filePolic
18
19
  return toCallToolResult(parsedInput, exportContract);
19
20
  const parsed = parsedInput.value;
20
21
  const bundle = session.exportEvidenceBundle();
21
- if (parsed.path === undefined)
22
- return toCallToolResult(ok(bundle), exportContract);
23
22
  const denied = await authorizeEvidencePath({
24
23
  authority: permissionAuthority,
25
24
  capability: "evidence_write",
@@ -35,10 +34,38 @@ const registerExportEvidenceTool = ({ server, session, exportContract, filePolic
35
34
  path: written.value.path,
36
35
  bytes: written.value.bytes,
37
36
  records: bundle.records.length,
37
+ unknowns: bundle.unknowns.length,
38
38
  }), exportContract)
39
39
  : toCallToolResult(written, exportContract);
40
40
  });
41
41
  };
42
+ const registerSnapshotEvidenceTool = ({ server, session, snapshotContract, }) => {
43
+ server.registerTool(snapshotContract.name, toolRegistrationOptions(snapshotContract), (input) => {
44
+ const parsed = safeParseToolInput(snapshotEvidenceBundleInputSchema, input, snapshotContract.name);
45
+ if (!parsed.ok)
46
+ return toCallToolResult(parsed, snapshotContract);
47
+ const snapshot = session.snapshotEvidenceBundle();
48
+ if (!snapshot.ok)
49
+ return toCallToolResult(snapshot, snapshotContract);
50
+ const value = {
51
+ bundle_digest: snapshot.value.bundleDigest,
52
+ bundle_version: snapshot.value.bundleVersion,
53
+ bytes: snapshot.value.bytes,
54
+ records: snapshot.value.records,
55
+ unknowns: snapshot.value.unknowns,
56
+ bundle_uri: snapshot.value.uri,
57
+ };
58
+ return toCallToolResult(ok(value), snapshotContract, {
59
+ resourceLinks: [
60
+ {
61
+ uri: snapshot.value.uri,
62
+ name: snapshot.value.bundleDigest,
63
+ description: "Immutable session-retained Evidence v2 bundle",
64
+ },
65
+ ],
66
+ });
67
+ });
68
+ };
42
69
  const registerImportEvidenceTool = ({ server, session, importContract, filePolicy, permissionAuthority, }) => {
43
70
  server.registerTool(importContract.name, toolRegistrationOptions(importContract), async (input) => {
44
71
  const parsedInput = safeParseToolInput(importEvidenceBundleInputSchema, input, importContract.name);
@@ -58,8 +85,6 @@ const registerImportEvidenceTool = ({ server, session, importContract, filePolic
58
85
  if (!loaded.ok)
59
86
  return toCallToolResult(loaded, importContract);
60
87
  const imported = session.importEvidenceBundle(loaded.value);
61
- if (imported.ok && imported.value > 0)
62
- server.sendResourceListChanged();
63
88
  return imported.ok
64
89
  ? toCallToolResult(ok({
65
90
  imported: imported.value,
@@ -93,22 +118,53 @@ export const registerUnknownTools = ({ server, session, contracts, }) => {
93
118
  if (!parsed.ok)
94
119
  return toCallToolResult(parsed, listContract);
95
120
  const filters = parsed.value;
96
- return toCallToolResult(ok(session.listUnknowns({
121
+ const all = session.listUnknowns({
97
122
  ...(filters.status === undefined ? {} : { status: filters.status }),
98
123
  ...(filters.severity === undefined
99
124
  ? {}
100
125
  : { severity: filters.severity }),
101
126
  ...(filters.domain === undefined ? {} : { domain: filters.domain }),
102
- })), listContract);
127
+ });
128
+ const items = all
129
+ .slice(filters.offset, filters.offset + filters.limit)
130
+ .map((unknown) => ({
131
+ unknown,
132
+ uri: `rea://unknown/${unknown.unknown_id}`,
133
+ }));
134
+ const nextOffset = filters.offset + items.length < all.length
135
+ ? filters.offset + items.length
136
+ : null;
137
+ return toCallToolResult(ok({
138
+ items,
139
+ offset: filters.offset,
140
+ limit: filters.limit,
141
+ total: all.length,
142
+ next_offset: nextOffset,
143
+ has_more: nextOffset !== null,
144
+ }), listContract, {
145
+ resourceLinks: items.map((unknown) => ({
146
+ uri: unknown.uri,
147
+ name: unknown.unknown.unknown_id,
148
+ description: `Current ${unknown.unknown.status} residual unknown revision`,
149
+ })),
150
+ });
103
151
  });
104
152
  server.registerTool(recordContract.name, toolRegistrationOptions(recordContract), (input) => {
105
153
  const parsed = safeParseToolInput(recordUnknownInputSchema, input, recordContract.name);
106
154
  if (!parsed.ok)
107
155
  return toCallToolResult(parsed, recordContract);
108
156
  const result = session.recordUnknown(parsed.value);
109
- if (result.ok)
110
- server.sendResourceListChanged();
111
- return toCallToolResult(result, recordContract);
157
+ return result.ok
158
+ ? toCallToolResult(result, recordContract, {
159
+ resourceLinks: [
160
+ {
161
+ uri: `rea://unknown/${result.value.unknown_id}`,
162
+ name: result.value.unknown_id,
163
+ description: "Current residual unknown head",
164
+ },
165
+ ],
166
+ })
167
+ : toCallToolResult(result, recordContract);
112
168
  });
113
169
  server.registerTool(updateContract.name, toolRegistrationOptions(updateContract), (input) => {
114
170
  const parsed = safeParseToolInput(updateUnknownInputSchema, input, updateContract.name);
@@ -116,8 +172,22 @@ export const registerUnknownTools = ({ server, session, contracts, }) => {
116
172
  return toCallToolResult(parsed, updateContract);
117
173
  const result = session.updateUnknown(parsed.value);
118
174
  if (result.ok)
119
- server.sendResourceListChanged();
120
- return toCallToolResult(result, updateContract);
175
+ void server.server
176
+ .sendResourceUpdated({
177
+ uri: `rea://unknown/${result.value.unknown_id}`,
178
+ })
179
+ .catch(() => undefined);
180
+ return result.ok
181
+ ? toCallToolResult(result, updateContract, {
182
+ resourceLinks: [
183
+ {
184
+ uri: `rea://unknown/${result.value.unknown_id}`,
185
+ name: result.value.unknown_id,
186
+ description: "Updated residual unknown head",
187
+ },
188
+ ],
189
+ })
190
+ : toCallToolResult(result, updateContract);
121
191
  });
122
192
  server.registerTool(verifyContract.name, toolRegistrationOptions(verifyContract), (input) => {
123
193
  const parsed = safeParseToolInput(verifyUnknownResolutionInputSchema, input, verifyContract.name);
@@ -118,7 +118,7 @@ const recommendedActions = (open, alignment) => [
118
118
  : []),
119
119
  ...(!open
120
120
  ? [
121
- "Route the supplied target directly: ASAR/JavaScript to analyze_javascript_application, unfamiliar packages to inspect_artifact, managed PE/CLI to inspect_managed_artifact, browser/Electron runtimes to their list-target tools, and native binaries to open_binary.",
121
+ "For a supplied target, route by format: ASAR/JavaScript to analyze_javascript_application; archive/package to open_binary(path), then inspect_artifact or inventory_artifact on the active target; managed PE/CLI to inspect_managed_artifact; browser/Electron runtimes to their list-target tools; native binaries to open_binary.",
122
122
  ]
123
123
  : [
124
124
  "Continue from the active target with binary_overview or a bounded target-specific query.",
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { SESSION_TOOL_CONTRACTS } from "../contracts/toolContracts.js";
2
+ import { addressContextInputSchema, navigationContextInputSchema, SESSION_TOOL_CONTRACTS, } from "../contracts/toolContracts.js";
3
3
  import { toCallToolResult } from "./toolResult.js";
4
4
  import { logToolExecution } from "./toolLogging.js";
5
5
  import { ok } from "../domain/result.js";
@@ -27,6 +27,7 @@ import { registerReplayMachineTool } from "./registerReplayMachineTool.js";
27
27
  import { sessionAvailabilityPolicy, } from "./sessionAvailabilityPolicy.js";
28
28
  import { permissionFailure } from "./permissionFailure.js";
29
29
  import { registerCloseLifecycleTool } from "./registerCloseLifecycleTool.js";
30
+ import { getNavigationContext, inspectAddressContext, } from "../application/AnalysisContextQueries.js";
30
31
  const recordProcessResidualUnknowns = (session, scenario, evidence, residuals) => {
31
32
  if (scenario.unknown_registry_approved !== true)
32
33
  return ok(null);
@@ -171,11 +172,28 @@ const registerRecordAndReplayTools = (server, session, logger) => {
171
172
  });
172
173
  registerReplayMachineTool(server, logger);
173
174
  };
175
+ const registerContextTools = (server, session) => {
176
+ const navigationContract = SESSION_TOOL_CONTRACTS[20];
177
+ const addressContract = SESSION_TOOL_CONTRACTS[21];
178
+ server.registerTool(navigationContract.name, toolRegistrationOptions(navigationContract), async (input, context) => {
179
+ const parsed = safeParseToolInput(navigationContextInputSchema, input, navigationContract.name);
180
+ if (!parsed.ok)
181
+ return toCallToolResult(parsed, navigationContract);
182
+ return toCallToolResult(await getNavigationContext(session, parsed.value, context.mcpReq.signal), navigationContract);
183
+ });
184
+ server.registerTool(addressContract.name, toolRegistrationOptions(addressContract), async (input, context) => {
185
+ const parsed = safeParseToolInput(addressContextInputSchema, input, addressContract.name);
186
+ if (!parsed.ok)
187
+ return toCallToolResult(parsed, addressContract);
188
+ return toCallToolResult(await inspectAddressContext(session, parsed.value, context.mcpReq.signal), addressContract);
189
+ });
190
+ };
174
191
  export const registerSessionTools = (server, session, logger, options = {}) => {
175
192
  const processPolicy = options.processPolicy ?? DENY_PROCESS_POLICY;
176
193
  const evidenceFilePolicy = options.evidenceFilePolicy ?? DENY_EVIDENCE_FILE_POLICY;
177
194
  const analysisSnapshotFilePolicy = options.analysisSnapshotFilePolicy ?? DENY_EVIDENCE_FILE_POLICY;
178
195
  const [openContract, closeContract, statusContract, exportContract, importContract, captureContract, compareContract, compareArtifactsContract, compareFunctionsContract, compareBundlesContract, changedBehaviorContract, callPathContract, staticRuntimeContract, reconstructionContract,] = SESSION_TOOL_CONTRACTS;
196
+ const snapshotContract = SESSION_TOOL_CONTRACTS[19];
179
197
  registerLifecycleTools({
180
198
  server,
181
199
  session,
@@ -197,6 +215,7 @@ export const registerSessionTools = (server, session, logger, options = {}) => {
197
215
  session,
198
216
  exportContract,
199
217
  importContract,
218
+ snapshotContract,
200
219
  filePolicy: evidenceFilePolicy,
201
220
  ...(options.permissionAuthority === undefined
202
221
  ? {}
@@ -238,4 +257,5 @@ export const registerSessionTools = (server, session, logger, options = {}) => {
238
257
  : { permissionAuthority: options.permissionAuthority }),
239
258
  });
240
259
  registerRecordAndReplayTools(server, session, logger);
260
+ registerContextTools(server, session);
241
261
  };
@@ -1,9 +1,11 @@
1
1
  import { z } from "zod";
2
+ const ADVERTISED_INPUT_SCHEMAS = new WeakMap();
3
+ const ADVERTISED_OUTPUT_SCHEMAS = new WeakMap();
2
4
  /** Project the public contract fields accepted by MCP tool registration. */
3
5
  export const toolRegistrationOptions = (contract) => ({
4
6
  title: contract.title,
5
7
  description: contract.description,
6
- inputSchema: advertisedInputSchema(contract.inputSchema),
8
+ inputSchema: advertisedInputSchema(contract),
7
9
  outputSchema: advertisedOutputSchema(contract.outputSchema),
8
10
  annotations: contract.annotations,
9
11
  });
@@ -12,14 +14,14 @@ export const toolRegistrationOptions = (contract) => ({
12
14
  * The SDK otherwise converts validation failures to text-only JSON-RPC errors
13
15
  * before REA can return its stable structured error envelope.
14
16
  */
15
- const advertisedInputSchema = (schema) => ({
17
+ const advertisedInputSchema = (contract) => ({
16
18
  "~standard": {
17
19
  version: 1,
18
20
  vendor: "rea",
19
21
  validate: (input) => ({ value: input }),
20
22
  jsonSchema: {
21
- input: () => describeProperties(closeObjectSchemas(inputJsonSchema(schema))),
22
- output: () => describeProperties(closeObjectSchemas(inputJsonSchema(schema))),
23
+ input: () => advertisedInputJsonSchema(contract),
24
+ output: () => advertisedInputJsonSchema(contract),
23
25
  },
24
26
  },
25
27
  });
@@ -32,12 +34,25 @@ const advertisedOutputSchema = (schema) => {
32
34
  "~standard": {
33
35
  ...standard,
34
36
  jsonSchema: {
35
- input: () => describeRootProperties(closeObjectSchemas(converter({ target: "draft-2020-12" }))),
36
- output: () => describeRootProperties(closeObjectSchemas(converter({ target: "draft-2020-12" }))),
37
+ input: () => advertisedOutputJsonSchema(schema, converter),
38
+ output: () => advertisedOutputJsonSchema(schema, converter),
37
39
  },
38
40
  },
39
41
  };
40
42
  };
43
+ const advertisedInputJsonSchema = (contract) => cachedJsonSchema(ADVERTISED_INPUT_SCHEMAS, contract.inputSchema, () => ({
44
+ ...describeProperties(closeObjectSchemas(inputJsonSchema(contract.inputSchema))),
45
+ examples: contract.examples.map(({ input }) => input),
46
+ }));
47
+ const advertisedOutputJsonSchema = (schema, converter) => cachedJsonSchema(ADVERTISED_OUTPUT_SCHEMAS, schema, () => describeRootProperties(closeObjectSchemas(converter({ target: "draft-2020-12" }))));
48
+ const cachedJsonSchema = (cache, schema, create) => {
49
+ const cached = cache.get(schema);
50
+ if (cached !== undefined)
51
+ return cached;
52
+ const created = create();
53
+ cache.set(schema, created);
54
+ return created;
55
+ };
41
56
  const inputJsonSchema = (schema) => {
42
57
  const converter = schema["~standard"].jsonSchema?.input;
43
58
  if (converter === undefined)
@@ -49,6 +49,14 @@ const successResult = (value, contract, options) => {
49
49
  : (compactEvidence(value, options.evidenceTextProjection) ??
50
50
  options.evidenceTextProjection)),
51
51
  },
52
+ ...(hasResourceLinks(value, contract, options)
53
+ ? [
54
+ {
55
+ type: "text",
56
+ text: "Full detail is available through MCP resources/read. Copy the opaque URI exactly. In Codex, call read_mcp_resource.",
57
+ },
58
+ ]
59
+ : []),
52
60
  ...evidenceResourceLinks(value, contract.kind === "session" ||
53
61
  options.evidenceResourcesAvailable === true),
54
62
  ...(options.resourceLinks ?? []).map((resource) => ({
@@ -60,6 +68,10 @@ const successResult = (value, contract, options) => {
60
68
  structuredContent: candidate,
61
69
  };
62
70
  };
71
+ const hasResourceLinks = (value, contract, options) => options.resourceLinks !== undefined && options.resourceLinks.length > 0
72
+ ? true
73
+ : evidenceResourceLinks(value, contract.kind === "session" ||
74
+ options.evidenceResourcesAvailable === true).length > 0;
63
75
  const compactEvidence = (value, resultProjection) => {
64
76
  if (typeof value !== "object" ||
65
77
  value === null ||