rea-agents 2.1.0 → 2.2.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 (48) hide show
  1. package/README.md +29 -10
  2. package/dist/application/AndroidApplicationService.js +17 -0
  3. package/dist/application/AppleApplicationService.js +17 -0
  4. package/dist/application/ClientRegistrationStatus.js +11 -4
  5. package/dist/application/InventoryProjectionEvidence.js +41 -0
  6. package/dist/application/InvestigationProviders.js +12 -0
  7. package/dist/application/ProcessPairedExperiment.js +72 -0
  8. package/dist/application/Setup.js +3 -3
  9. package/dist/application/SetupClientConfiguration.js +10 -6
  10. package/dist/application/SetupPlan.js +3 -2
  11. package/dist/application/SetupSkill.js +44 -40
  12. package/dist/application/Uninstall.js +7 -1
  13. package/dist/application/Upgrade.js +16 -1
  14. package/dist/cli/setupCommands.js +8 -3
  15. package/dist/cliSetup.js +29 -12
  16. package/dist/contracts/electronToolContracts.js +51 -3
  17. package/dist/contracts/managedToolContracts.js +7 -2
  18. package/dist/contracts/sessionStatusContract.js +26 -0
  19. package/dist/contracts/toolContractTypes.js +12 -1
  20. package/dist/contracts/toolContracts.js +3 -10
  21. package/dist/contracts/toolOutputSchemaGroups.js +51 -9
  22. package/dist/contracts/toolOutputSchemaPrimitives.js +33 -32
  23. package/dist/domain/androidApplication.js +206 -0
  24. package/dist/domain/appleApplication.js +221 -0
  25. package/dist/domain/boundedCartesianProjection.js +15 -0
  26. package/dist/domain/processComparison.js +19 -0
  27. package/dist/domain/processPairedExperiment.js +128 -0
  28. package/dist/domain/replayMachine.js +316 -0
  29. package/dist/domain/replayMachineRuntime.js +215 -0
  30. package/dist/domain/runtimeIdentification.js +210 -0
  31. package/dist/evaluation/CodexAgentEval.js +132 -0
  32. package/dist/generatedPackageMetadata.js +2 -2
  33. package/dist/identity.js +2 -1
  34. package/dist/server/createServer.js +5 -2
  35. package/dist/server/javascriptApplicationResult.js +85 -0
  36. package/dist/server/registerElectronTools.js +19 -5
  37. package/dist/server/registerEvidenceResources.js +2 -0
  38. package/dist/server/registerJavaScriptApplicationGraphResource.js +69 -0
  39. package/dist/server/registerManagedTools.js +18 -1
  40. package/dist/server/registerSessionStatusTool.js +95 -25
  41. package/dist/server/toolResult.js +10 -4
  42. package/package.json +2 -1
  43. package/skills/reverse-engineer-anything/SKILL.md +68 -227
  44. package/skills/reverse-engineer-anything/references/controlled-replay.md +12 -0
  45. package/skills/reverse-engineer-anything/references/evidence-workflows.md +33 -0
  46. package/skills/reverse-engineer-anything/references/javascript-applications.md +25 -0
  47. package/skills/reverse-engineer-anything/references/native-and-artifacts.md +36 -0
  48. package/skills/reverse-engineer-anything/references/runtime-observation.md +24 -0
@@ -0,0 +1,210 @@
1
+ import { createHash } from "node:crypto";
2
+ import canonicalize from "canonicalize";
3
+ import { z } from "zod";
4
+ import { parseArtifactInventoryEvidence } from "./artifactInventoryEvidence.js";
5
+ import { evidenceSchema } from "./evidence.js";
6
+ const digestSchema = z.string().regex(/^[a-f0-9]{64}$/u);
7
+ const evidenceIdSchema = z.string().regex(/^ev_[a-f0-9]{64}$/u);
8
+ const runtimeFamilySchema = z.enum([
9
+ "android",
10
+ "apple",
11
+ "javascript",
12
+ "jvm",
13
+ "native",
14
+ "webassembly",
15
+ ]);
16
+ const observationSchema = z.strictObject({
17
+ path: z.string().min(1).max(4_096),
18
+ artifact_id: z.string().regex(/^art_[a-f0-9]{64}$/u),
19
+ sha256: digestSchema,
20
+ format: z.string().min(1).max(100),
21
+ });
22
+ /** Authenticated artifact inventory pages to classify by runtime family. */
23
+ const runtimeIdentificationInputSchema = z.strictObject({
24
+ inventory_evidence: z.array(evidenceSchema).min(1).max(100),
25
+ limits: z
26
+ .strictObject({
27
+ max_observations: z.number().int().min(1).max(10_000).default(1_000),
28
+ })
29
+ .default({ max_observations: 1_000 }),
30
+ });
31
+ /** Provider-neutral runtime identification with explicit tooling availability. */
32
+ export const runtimeIdentificationResultSchema = z.strictObject({
33
+ schema_version: z.literal(1),
34
+ identification_id: z.string().regex(/^rid_[a-f0-9]{64}$/u),
35
+ root_sha256: digestSchema,
36
+ root_format: z.string().min(1).max(100),
37
+ source_evidence_ids: z.array(evidenceIdSchema).min(1).max(100),
38
+ runtimes: z.array(z.strictObject({
39
+ family: runtimeFamilySchema,
40
+ inspection: z.enum([
41
+ "available",
42
+ "provider-missing",
43
+ "provider-selection-required",
44
+ ]),
45
+ provider_id: z.string().min(1).max(200).nullable(),
46
+ reason: z.string().min(1).max(1_000).nullable(),
47
+ observations: z.array(observationSchema),
48
+ omitted_observations: z.number().int().min(0),
49
+ })),
50
+ coverage: z.strictObject({
51
+ status: z.enum(["complete-within-inventory", "partial", "truncated"]),
52
+ inventory_complete: z.boolean(),
53
+ omitted_observations: z.number().int().min(0),
54
+ }),
55
+ limitations: z.array(z.string().min(1).max(4_096)).max(100),
56
+ });
57
+ const PROVIDERS = {
58
+ android: {
59
+ inspection: "available",
60
+ id: "rea-android-application",
61
+ reason: null,
62
+ },
63
+ apple: { inspection: "available", id: "rea-apple-application", reason: null },
64
+ javascript: {
65
+ inspection: "available",
66
+ id: "rea-javascript-application",
67
+ reason: null,
68
+ },
69
+ jvm: {
70
+ inspection: "provider-missing",
71
+ id: null,
72
+ reason: "Install or configure a JVM bytecode-analysis provider.",
73
+ },
74
+ native: {
75
+ inspection: "provider-selection-required",
76
+ id: null,
77
+ reason: "Select an available native deep-analysis provider for this target and host.",
78
+ },
79
+ webassembly: {
80
+ inspection: "provider-missing",
81
+ id: null,
82
+ reason: "Install or configure a WebAssembly semantic-analysis provider.",
83
+ },
84
+ };
85
+ /** Identify runtime families without executing or semantically decoding target bytes. */
86
+ export const identifyRuntimes = (input) => {
87
+ const parsed = runtimeIdentificationInputSchema.parse(input);
88
+ const { evidence, inventory } = parseArtifactInventoryEvidence(parsed.inventory_evidence);
89
+ const nodes = new Map(inventory.nodes.map((node) => [node.artifact_id, node]));
90
+ const grouped = new Map();
91
+ const add = (family, observation) => {
92
+ const observations = grouped.get(family) ?? [];
93
+ observations.push(observation);
94
+ grouped.set(family, observations);
95
+ };
96
+ for (const occurrence of inventory.occurrences) {
97
+ if (occurrence.artifact_id === null)
98
+ continue;
99
+ const node = nodes.get(occurrence.artifact_id);
100
+ if (node === undefined)
101
+ throw new TypeError("Runtime observation has no artifact node");
102
+ const observation = {
103
+ path: occurrence.logical_path,
104
+ artifact_id: node.artifact_id,
105
+ sha256: node.sha256,
106
+ format: node.format,
107
+ };
108
+ for (const family of familiesFor(node.format, occurrence.logical_path))
109
+ add(family, observation);
110
+ }
111
+ const rootObservation = inventory.nodes.find(({ sha256 }) => sha256 === inventory.manifest.root_sha256);
112
+ if (rootObservation !== undefined)
113
+ for (const family of familiesFor(inventory.manifest.root_format, "."))
114
+ add(family, {
115
+ path: ".",
116
+ artifact_id: rootObservation.artifact_id,
117
+ sha256: rootObservation.sha256,
118
+ format: rootObservation.format,
119
+ });
120
+ let remaining = parsed.limits.max_observations;
121
+ let omitted = 0;
122
+ const runtimes = [...grouped.entries()]
123
+ .sort(([left], [right]) => compare(left, right))
124
+ .map(([family, values]) => {
125
+ const unique = deduplicate(values);
126
+ const observations = unique.slice(0, remaining);
127
+ remaining -= observations.length;
128
+ const omittedObservations = unique.length - observations.length;
129
+ omitted += omittedObservations;
130
+ const provider = providerFor(family, inventory.manifest.root_format);
131
+ return {
132
+ family,
133
+ inspection: provider.inspection,
134
+ provider_id: provider.id,
135
+ reason: provider.reason,
136
+ observations,
137
+ omitted_observations: omittedObservations,
138
+ };
139
+ });
140
+ const limitations = [
141
+ ...(!inventory.complete
142
+ ? [
143
+ "Source inventory pages are incomplete; absent runtimes remain unknown.",
144
+ ]
145
+ : []),
146
+ ...(omitted > 0
147
+ ? [`${String(omitted)} runtime observations were omitted by the limit.`]
148
+ : []),
149
+ "Runtime families are identified from exact artifact formats and paths; semantic claims require the named provider.",
150
+ ];
151
+ const withoutId = {
152
+ schema_version: 1,
153
+ root_sha256: inventory.manifest.root_sha256,
154
+ root_format: inventory.manifest.root_format,
155
+ source_evidence_ids: evidence
156
+ .map(({ evidence_id: id }) => id)
157
+ .sort(compare),
158
+ runtimes,
159
+ coverage: {
160
+ status: omitted > 0
161
+ ? "truncated"
162
+ : inventory.complete
163
+ ? "complete-within-inventory"
164
+ : "partial",
165
+ inventory_complete: inventory.complete,
166
+ omitted_observations: omitted,
167
+ },
168
+ limitations,
169
+ };
170
+ return runtimeIdentificationResultSchema.parse({
171
+ ...withoutId,
172
+ identification_id: `rid_${digest(withoutId)}`,
173
+ });
174
+ };
175
+ const providerFor = (family, rootFormat) => family === "android" && rootFormat !== "apk"
176
+ ? {
177
+ inspection: "provider-selection-required",
178
+ id: null,
179
+ reason: "Extract or select an APK root before using the Android application provider.",
180
+ }
181
+ : PROVIDERS[family];
182
+ const familiesFor = (format, path) => {
183
+ const families = new Set();
184
+ if (format === "apk" ||
185
+ format === "dex" ||
186
+ /(?:^|\/)[^/]+\.dex$/iu.test(path) ||
187
+ /(?:^|\/)AndroidManifest\.xml$/u.test(path))
188
+ families.add("android");
189
+ if (format === "ipa")
190
+ families.add("apple");
191
+ if (format === "javascript-bundle")
192
+ families.add("javascript");
193
+ if (format === "jvm-class" || /(?:^|\/)[^/]+\.class$/iu.test(path))
194
+ families.add("jvm");
195
+ if (["elf", "mach-o", "mach-o-universal", "pe"].includes(format))
196
+ families.add("native");
197
+ if (format === "webassembly" || /(?:^|\/)[^/]+\.wasm$/iu.test(path))
198
+ families.add("webassembly");
199
+ return [...families];
200
+ };
201
+ const deduplicate = (values) => [
202
+ ...new Map(values.map((value) => [`${value.path}\0${value.artifact_id}`, value])).values(),
203
+ ].sort((left, right) => compare(left.path, right.path));
204
+ const compare = (left, right) => left < right ? -1 : left > right ? 1 : 0;
205
+ const digest = (value) => {
206
+ const encoded = canonicalize(value);
207
+ if (encoded === undefined)
208
+ throw new TypeError("Runtime identification is not canonical JSON");
209
+ return createHash("sha256").update(encoded).digest("hex");
210
+ };
@@ -0,0 +1,132 @@
1
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2
+ const record = (value) => isRecord(value) ? value : undefined;
3
+ const textValue = (value) => typeof value === "string" && value.length > 0 ? value : undefined;
4
+ const numberValue = (value) => typeof value === "number" && Number.isFinite(value) ? value : 0;
5
+ const canonicalValue = (value) => {
6
+ if (Array.isArray(value))
7
+ return value.map(canonicalValue);
8
+ const object = record(value);
9
+ if (object === undefined)
10
+ return value;
11
+ return Object.fromEntries(Object.entries(object)
12
+ .sort(([left], [right]) => left.localeCompare(right))
13
+ .map(([key, child]) => [key, canonicalValue(child)]));
14
+ };
15
+ const evidenceIdsFrom = (value) => {
16
+ const encoded = JSON.stringify(value);
17
+ if (encoded === undefined)
18
+ return [];
19
+ return [...new Set(encoded.match(/ev_[a-f0-9]{64}/gu) ?? [])];
20
+ };
21
+ const toolResultFailed = (item) => {
22
+ if (item.status === "failed" ||
23
+ (item.error !== undefined && item.error !== null))
24
+ return true;
25
+ const result = record(item.result ?? item.output);
26
+ const structured = record(result?.structured_content ?? result?.structuredContent);
27
+ return result?.isError === true || structured?.error !== undefined;
28
+ };
29
+ const callFromItem = (value) => {
30
+ const item = record(value);
31
+ if (item?.type !== "mcp_tool_call")
32
+ return undefined;
33
+ const tool = textValue(item.tool) ?? textValue(item.name);
34
+ if (tool === undefined)
35
+ return undefined;
36
+ return {
37
+ id: textValue(item.id) ?? null,
38
+ server: textValue(item.server) ?? textValue(item.server_name) ?? null,
39
+ tool,
40
+ arguments: item.arguments ?? item.input ?? {},
41
+ evidenceIds: evidenceIdsFrom(item.result ?? item.output),
42
+ error: toolResultFailed(item),
43
+ };
44
+ };
45
+ const agentTextFromItem = (value) => {
46
+ const item = record(value);
47
+ if (item?.type !== "agent_message")
48
+ return undefined;
49
+ const direct = textValue(item.text) ?? textValue(item.message);
50
+ if (direct !== undefined)
51
+ return direct;
52
+ if (!Array.isArray(item.content))
53
+ return undefined;
54
+ const parts = item.content.flatMap((part) => {
55
+ const object = record(part);
56
+ const text = object === undefined ? undefined : textValue(object.text);
57
+ return text === undefined ? [] : [text];
58
+ });
59
+ return parts.length === 0 ? undefined : parts.join("\n");
60
+ };
61
+ const consumeEvent = (state, value) => {
62
+ const event = record(value);
63
+ if (event === undefined)
64
+ return;
65
+ const completedItem = event.type === "item.completed" ? event.item : undefined;
66
+ const call = callFromItem(completedItem) ??
67
+ (event.type === "mcp_tool_call" ? callFromItem(event) : undefined);
68
+ if (call !== undefined) {
69
+ const identity = call.id ??
70
+ `${call.server ?? ""}:${call.tool}:${JSON.stringify(canonicalValue(call.arguments))}`;
71
+ if (!state.seenCallIds.has(identity)) {
72
+ state.seenCallIds.add(identity);
73
+ state.calls.push(call);
74
+ }
75
+ }
76
+ const message = agentTextFromItem(completedItem);
77
+ if (message !== undefined)
78
+ state.finalMessages.push(message);
79
+ if (event.type !== "turn.completed")
80
+ return;
81
+ const usage = record(event.usage);
82
+ state.inputTokens = Math.max(state.inputTokens, numberValue(usage?.input_tokens));
83
+ state.cachedInputTokens = Math.max(state.cachedInputTokens, numberValue(usage?.cached_input_tokens));
84
+ state.outputTokens = Math.max(state.outputTokens, numberValue(usage?.output_tokens));
85
+ };
86
+ /** Score agent routing, repetition, model usage, and epistemic completion. */
87
+ export const evaluateCodexEvents = (events, expectedFirstTool, options = {}) => {
88
+ const state = {
89
+ calls: [],
90
+ seenCallIds: new Set(),
91
+ finalMessages: [],
92
+ inputTokens: 0,
93
+ cachedInputTokens: 0,
94
+ outputTokens: 0,
95
+ };
96
+ for (const event of events)
97
+ consumeEvent(state, event);
98
+ const reaCalls = state.calls.filter(({ server }) => server === null || server.toLowerCase() === "rea");
99
+ const signatures = new Map();
100
+ for (const call of reaCalls) {
101
+ const signature = `${call.tool}:${JSON.stringify(canonicalValue(call.arguments))}`;
102
+ signatures.set(signature, (signatures.get(signature) ?? 0) + 1);
103
+ }
104
+ const repeatedCallCount = [...signatures.values()].reduce((total, count) => total + Math.max(0, count - 1), 0);
105
+ const finalMessage = state.finalMessages.at(-1) ?? "";
106
+ const evidenceIds = [
107
+ ...new Set(reaCalls.flatMap(({ evidenceIds: ids }) => ids)),
108
+ ];
109
+ const finalCitesEvidence = evidenceIds.some((evidenceId) => finalMessage.includes(evidenceId));
110
+ const normalizedFinalMessage = finalMessage.toLocaleLowerCase("en-US");
111
+ const contentCriteriaMet = (options.requiredAnswerTermGroups ?? []).every((terms) => terms.some((term) => normalizedFinalMessage.includes(term.toLocaleLowerCase("en-US"))));
112
+ const authorityHonesty = /\b(evidence|observed|inferred|unknown|unavailable|limitation|authority|not configured|could not|requires approval)\b/iu.test(finalMessage);
113
+ return {
114
+ naturalUse: reaCalls.length > 0,
115
+ correctFirstTool: reaCalls[0]?.tool === expectedFirstTool,
116
+ firstTool: reaCalls[0]?.tool ?? null,
117
+ reaCalls,
118
+ repeatedCallCount,
119
+ inputTokens: state.inputTokens,
120
+ cachedInputTokens: state.cachedInputTokens,
121
+ outputTokens: state.outputTokens,
122
+ finalMessage,
123
+ evidenceIds,
124
+ finalCitesEvidence,
125
+ contentCriteriaMet,
126
+ completionQuality: finalMessage.trim().length >= 80 &&
127
+ authorityHonesty &&
128
+ contentCriteriaMet &&
129
+ (options.requireEvidence !== true || finalCitesEvidence),
130
+ authorityHonesty,
131
+ };
132
+ };
@@ -1,9 +1,9 @@
1
1
  /** Generated from package.json and package-lock.json; do not edit. */
2
2
  export const PACKAGE_METADATA = {
3
3
  name: "rea-agents",
4
- version: "2.1.0",
4
+ version: "2.2.0", // x-release-please-version
5
5
  serverSdkVersion: "2.0.0-beta.4",
6
6
  clientSdkVersion: "2.0.0-beta.4",
7
7
  coreSdkVersion: "2.0.0-beta.4",
8
- skillVersion: "22",
8
+ skillVersion: "23",
9
9
  };
package/dist/identity.js CHANGED
@@ -7,9 +7,10 @@ export const PRODUCT_IDENTITY = {
7
7
  displayName: "REA",
8
8
  packageName: PACKAGE_METADATA.name,
9
9
  packageSpecifier: `${PACKAGE_METADATA.name}@latest`,
10
+ registrationPackageSpecifier: `${PACKAGE_METADATA.name}@${PACKAGE_METADATA.version}`,
10
11
  packageVersion: PACKAGE_METADATA.version,
11
12
  cliBinary: "rea",
12
- mcpCommand: `npx -y ${PACKAGE_METADATA.name}@latest mcp`,
13
+ mcpCommand: `npx -y ${PACKAGE_METADATA.name}@${PACKAGE_METADATA.version} mcp`,
13
14
  mcpServerKey: "rea",
14
15
  skillName: "reverse-engineer-anything",
15
16
  skillVersion: PACKAGE_METADATA.skillVersion,
@@ -15,6 +15,8 @@ import { createServerIdentity } from "../serverIdentity.js";
15
15
  import { registerBrowserTools } from "./registerBrowserTools.js";
16
16
  import { registerElectronTools } from "./registerElectronTools.js";
17
17
  import { registerApplicationTools } from "./registerApplicationTools.js";
18
+ const TARGET_FREE_INSTRUCTIONS = "REA analyzes shipped artifacts and approved runtime evidence; use repository tools for source. Route: ASAR/JavaScript -> analyze_javascript_application; archive/package -> inventory_artifact; managed PE/CLI -> inspect_managed_artifact; browser/Electron runtime -> list_browser_targets/list_electron_targets; native binary/database -> open_binary, then binary_overview. Start with summaries and cite Evidence IDs. Never repeat identical analysis or read full Evidence unless a missing detail requires it.";
19
+ const ACTIVE_TARGET_INSTRUCTIONS = "REA analyzes the active reverse-engineering target. Start native analysis with binary_overview, then narrow with analyze_function, literal search, callers, callees, and xrefs. Prefer summary views, never repeat an identical call, and read full Evidence only when the task requires it.";
18
20
  import { LinuxJavaScriptReplayRunner } from "../replay/LinuxJavaScriptReplayRunner.js";
19
21
  import { SystemJavaScriptReplayHost } from "../replay/SystemJavaScriptReplayHost.js";
20
22
  import { PROCESS_CAPTURE_ELICITATION_POLICY, } from "./ProcessCaptureElicitation.js";
@@ -45,8 +47,8 @@ export const createServer = (analysis, session, options = {}) => {
45
47
  },
46
48
  requestState: { verify: processCaptureStateCodec.verify },
47
49
  instructions: session === undefined
48
- ? "Reverse-engineering tools for an active analysis target. Start with binary_overview."
49
- : "Reverse-engineering tools for configured analysis providers. Open a target with open_binary, then start with binary_overview.",
50
+ ? ACTIVE_TARGET_INSTRUCTIONS
51
+ : TARGET_FREE_INSTRUCTIONS,
50
52
  });
51
53
  server.server.onclose = () => {
52
54
  permissionAuthority?.clearSessionGrants();
@@ -168,6 +170,7 @@ const registerBinaryAnalysisTools = ({ server, analysis, session, options, logge
168
170
  logger,
169
171
  activeTarget,
170
172
  recordEvidence,
173
+ session,
171
174
  });
172
175
  if (session !== undefined)
173
176
  registerManagedWorkflowTools(server, {
@@ -0,0 +1,85 @@
1
+ import { javascriptApplicationAnalysisResultSchema } from "../domain/javascriptApplicationAnalysis.js";
2
+ import { compareCodePoints } from "../domain/javascriptApplicationGraph.js";
3
+ import { javascriptApplicationAnalysisSummarySchema } from "../contracts/electronToolContracts.js";
4
+ const DEFAULT_GRAPH_PAGE_LIMIT = 100;
5
+ const ROOT_SAMPLE_LIMIT = 10;
6
+ const TOP_FINDING_LIMIT = 20;
7
+ const FINDING_PRIORITY = {
8
+ "browser-window": 0,
9
+ "electron-preload": 1,
10
+ "context-bridge-api": 2,
11
+ "ipc-channel": 3,
12
+ "ipc-handler": 4,
13
+ "electron-utility": 5,
14
+ endpoint: 6,
15
+ storage: 7,
16
+ worker: 8,
17
+ "service-worker": 9,
18
+ "native-addon": 10,
19
+ "native-export": 11,
20
+ "source-map": 12,
21
+ };
22
+ /** Project complete JavaScript application Evidence into a bounded agent result. */
23
+ export const summarizeJavaScriptApplicationEvidence = (evidence) => {
24
+ const result = javascriptApplicationAnalysisResultSchema.parse(evidence.normalized_result);
25
+ const nodeKinds = new Map();
26
+ for (const node of result.graph.nodes)
27
+ nodeKinds.set(node.kind, (nodeKinds.get(node.kind) ?? 0) + 1);
28
+ const rootItems = result.graph.root_node_ids.slice(0, ROOT_SAMPLE_LIMIT);
29
+ const pageRoot = `rea://evidence/${evidence.evidence_id}/application-graph`;
30
+ const topFindings = [...result.graph.nodes]
31
+ .sort((left, right) => (FINDING_PRIORITY[left.kind] ?? Number.MAX_SAFE_INTEGER) -
32
+ (FINDING_PRIORITY[right.kind] ?? Number.MAX_SAFE_INTEGER) ||
33
+ compareCodePoints(left.node_id, right.node_id))
34
+ .slice(0, TOP_FINDING_LIMIT)
35
+ .map((node) => {
36
+ const observation = node.observations[0];
37
+ if (observation === undefined)
38
+ throw new TypeError("Application graph node has no observation");
39
+ return {
40
+ node_id: node.node_id,
41
+ kind: node.kind,
42
+ label: observation.label,
43
+ authority: observation.evidence.authority,
44
+ state: observation.evidence.state,
45
+ confidence: observation.evidence.confidence,
46
+ location: observation.evidence.location,
47
+ };
48
+ });
49
+ const { graph: _graph, ...summary } = result;
50
+ const unknowns = new Set([
51
+ ...result.limitations,
52
+ ...result.graph.limitations,
53
+ ]);
54
+ if (result.statistics.parse_failures > 0)
55
+ unknowns.add(`${String(result.statistics.parse_failures)} JavaScript file(s) could not be parsed.`);
56
+ if (result.statistics.truncated_scopes > 0)
57
+ unknowns.add(`${String(result.statistics.truncated_scopes)} analysis scope(s) were truncated.`);
58
+ if (result.graph.coverage.status !== "complete")
59
+ unknowns.add(`Application graph coverage is ${result.graph.coverage.status}; omitted facts remain unknown.`);
60
+ return javascriptApplicationAnalysisSummarySchema.parse({
61
+ ...summary,
62
+ unknowns: [...unknowns],
63
+ graph: {
64
+ graph_id: result.graph.graph_id,
65
+ node_count: result.graph.nodes.length,
66
+ edge_count: result.graph.edges.length,
67
+ roots: {
68
+ items: rootItems,
69
+ total: result.graph.root_node_ids.length,
70
+ truncated: rootItems.length < result.graph.root_node_ids.length,
71
+ },
72
+ node_kinds: [...nodeKinds]
73
+ .sort(([left], [right]) => compareCodePoints(left, right))
74
+ .map(([kind, count]) => ({ kind, count })),
75
+ top_findings: topFindings,
76
+ coverage: result.graph.coverage,
77
+ limitations: result.graph.limitations,
78
+ pages: {
79
+ nodes: `${pageRoot}/nodes/offset/0/limit/${String(DEFAULT_GRAPH_PAGE_LIMIT)}`,
80
+ edges: `${pageRoot}/edges/offset/0/limit/${String(DEFAULT_GRAPH_PAGE_LIMIT)}`,
81
+ page_limit: DEFAULT_GRAPH_PAGE_LIMIT,
82
+ },
83
+ },
84
+ });
85
+ };
@@ -1,15 +1,15 @@
1
1
  import { analyzeJavaScriptApplicationValidated } from "../application/JavaScriptApplicationService.js";
2
2
  import { reconcileJavaScriptRuntimeEvidenceValidated } from "../application/JavaScriptRuntimeReconciliationService.js";
3
3
  import { inspectElectronPage, listElectronTargets, } from "../application/ElectronObservationService.js";
4
- import { ELECTRON_TOOL_CONTRACTS } from "../contracts/electronToolContracts.js";
4
+ import { analyzeJavaScriptApplicationToolInputSchema, ELECTRON_TOOL_CONTRACTS, } from "../contracts/electronToolContracts.js";
5
5
  import { inspectElectronPageInputSchema, listElectronTargetsInputSchema, } from "../domain/electronObservation.js";
6
- import { analyzeJavaScriptApplicationInputSchema } from "../domain/javascriptApplicationAnalysis.js";
7
6
  import { reconcileJavaScriptRuntimeInputSchema } from "../domain/javascriptRuntimeReconciliationSchemas.js";
8
7
  import { logToolExecution } from "./toolLogging.js";
9
8
  import { toCallToolResult } from "./toolResult.js";
10
9
  import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
11
10
  import { safeParseToolInput } from "./toolInputValidation.js";
12
11
  import { mcpProgressReporter } from "./mcpProgress.js";
12
+ import { summarizeJavaScriptApplicationEvidence } from "./javascriptApplicationResult.js";
13
13
  /** Register Electron tools even when provider or permission policy is absent. */
14
14
  export const registerElectronTools = (server, options) => {
15
15
  const [listContract, inspectContract, analyzeContract, reconcileContract] = ELECTRON_TOOL_CONTRACTS;
@@ -30,8 +30,14 @@ export const registerElectronTools = (server, options) => {
30
30
  });
31
31
  registerElectronTool(server, options, {
32
32
  contract: analyzeContract,
33
- schema: analyzeJavaScriptApplicationInputSchema,
33
+ schema: analyzeJavaScriptApplicationToolInputSchema,
34
34
  execute: (parsed, { signal, progress }) => analyzeJavaScriptApplicationValidated(options.permissionAuthority, parsed, { signal, progress }),
35
+ projectEvidence: (evidence, parsed) => {
36
+ const summary = summarizeJavaScriptApplicationEvidence(evidence);
37
+ return parsed.detail === "full"
38
+ ? { structured: evidence.normalized_result, text: summary }
39
+ : { structured: summary };
40
+ },
35
41
  });
36
42
  registerElectronTool(server, options, {
37
43
  contract: reconcileContract,
@@ -50,14 +56,22 @@ const registerElectronTool = (server, options, spec) => {
50
56
  }));
51
57
  if (!result.ok)
52
58
  return toCallToolResult(result, spec.contract);
53
- return evidenceResult(options, spec.contract, result.value);
59
+ return evidenceResult(options, spec.contract, result.value, spec.projectEvidence?.(result.value, parsedInput.value));
54
60
  });
55
61
  };
56
- const evidenceResult = (options, contract, evidence) => {
62
+ const evidenceResult = (options, contract, evidence, projection) => {
57
63
  const recorded = options.recordEvidence?.(evidence);
58
64
  return recorded !== undefined && !recorded.ok
59
65
  ? toCallToolResult(recorded, contract)
60
66
  : toCallToolResult({ ok: true, value: evidence }, contract, {
61
67
  evidenceResourcesAvailable: recorded !== undefined,
68
+ ...(projection === undefined
69
+ ? {}
70
+ : {
71
+ evidenceResultProjection: projection.structured,
72
+ ...(projection.text === undefined
73
+ ? {}
74
+ : { evidenceTextProjection: projection.text }),
75
+ }),
62
76
  });
63
77
  };
@@ -1,6 +1,7 @@
1
1
  import { ResourceNotFoundError, ResourceTemplate, } from "@modelcontextprotocol/server";
2
2
  import { createHash } from "node:crypto";
3
3
  import canonicalize from "canonicalize";
4
+ import { registerJavaScriptApplicationGraphResource } from "./registerJavaScriptApplicationGraphResource.js";
4
5
  const evidenceUri = (evidenceId) => `rea://evidence/${evidenceId}`;
5
6
  /** Expose immutable session-owned Evidence v2 records as MCP resources. */
6
7
  export const registerEvidenceResources = (server, session) => {
@@ -12,6 +13,7 @@ export const registerEvidenceResources = (server, session) => {
12
13
  registerArtifactPageResource(server, session);
13
14
  registerFunctionDossierResource(server, session);
14
15
  registerEvidenceSectionResource(server, session);
16
+ registerJavaScriptApplicationGraphResource(server, session);
15
17
  registerResidualUnknownResource(server, session);
16
18
  };
17
19
  const registerReconstructionCoverageResource = (server, session) => {
@@ -0,0 +1,69 @@
1
+ import { ResourceNotFoundError, ResourceTemplate, } from "@modelcontextprotocol/server";
2
+ import { javascriptApplicationAnalysisResultSchema } from "../domain/javascriptApplicationAnalysis.js";
3
+ /** Register deterministic pages over one authenticated JavaScript graph. */
4
+ export const registerJavaScriptApplicationGraphResource = (server, session) => {
5
+ server.registerResource("javascript-application-graph-page", new ResourceTemplate("rea://evidence/{evidenceId}/application-graph/{collection}/offset/{offset}/limit/{limit}", {
6
+ list: undefined,
7
+ complete: {
8
+ evidenceId: (prefix) => session
9
+ .exportEvidenceBundle()
10
+ .records.map(({ evidence_id }) => evidence_id)
11
+ .filter((evidenceId) => evidenceId.startsWith(prefix)),
12
+ collection: (prefix) => ["nodes", "edges"].filter((name) => name.startsWith(prefix)),
13
+ },
14
+ }), {
15
+ title: "JavaScript application graph page",
16
+ description: "One deterministic bounded node or edge page from authenticated JavaScript application Evidence.",
17
+ mimeType: "application/json",
18
+ }, (uri, variables) => {
19
+ const evidenceId = stringVariable(variables.evidenceId, uri.href);
20
+ const collection = stringVariable(variables.collection, uri.href);
21
+ if (collection !== "nodes" && collection !== "edges")
22
+ throw new ResourceNotFoundError(uri.href);
23
+ const offset = pageInteger(variables.offset, uri.href, 0, 100_000);
24
+ const limit = pageInteger(variables.limit, uri.href, 1, 500);
25
+ const evidence = session.evidenceById(evidenceId);
26
+ if (evidence === undefined ||
27
+ evidence.operation !== "analyze_javascript_application")
28
+ throw new ResourceNotFoundError(uri.href);
29
+ const analysis = javascriptApplicationAnalysisResultSchema.safeParse(evidence.normalized_result);
30
+ if (!analysis.success)
31
+ throw new ResourceNotFoundError(uri.href);
32
+ const values = analysis.data.graph[collection];
33
+ const items = values.slice(offset, offset + limit);
34
+ const nextOffset = offset + items.length;
35
+ return {
36
+ contents: [
37
+ {
38
+ uri: uri.href,
39
+ mimeType: "application/json",
40
+ text: JSON.stringify({
41
+ evidence_id: evidence.evidence_id,
42
+ graph_id: analysis.data.graph.graph_id,
43
+ collection,
44
+ items,
45
+ offset,
46
+ limit,
47
+ total: values.length,
48
+ next_offset: nextOffset < values.length ? nextOffset : null,
49
+ has_more: nextOffset < values.length,
50
+ }, null, 2),
51
+ },
52
+ ],
53
+ };
54
+ });
55
+ };
56
+ const stringVariable = (value, uri) => {
57
+ if (typeof value !== "string")
58
+ throw new ResourceNotFoundError(uri);
59
+ return value;
60
+ };
61
+ const pageInteger = (value, uri, minimum, maximum) => {
62
+ const text = stringVariable(value, uri);
63
+ if (!/^\d+$/u.test(text))
64
+ throw new ResourceNotFoundError(uri);
65
+ const parsed = Number(text);
66
+ if (!Number.isSafeInteger(parsed) || parsed < minimum || parsed > maximum)
67
+ throw new ResourceNotFoundError(uri);
68
+ return parsed;
69
+ };
@@ -1,6 +1,23 @@
1
1
  import { MANAGED_TOOL_CONTRACTS } from "../contracts/managedToolContracts.js";
2
+ import { err } from "../domain/result.js";
2
3
  import { registerEvidenceTools } from "./registerEvidenceTools.js";
3
4
  /** Register execution-free managed PE/CLI inspection. */
4
5
  export const registerManagedTools = (server, analysis, options) => {
5
- registerEvidenceTools(server, analysis, MANAGED_TOOL_CONTRACTS, options);
6
+ const targetAwareAnalysis = {
7
+ execute: async (operation, parameters, executionOptions) => {
8
+ const path = operation === "inspect_managed_artifact" &&
9
+ typeof parameters.path === "string"
10
+ ? parameters.path
11
+ : undefined;
12
+ if (path !== undefined && options.session !== undefined) {
13
+ const opened = await options.session.open(path, executionOptions?.signal === undefined
14
+ ? undefined
15
+ : { signal: executionOptions.signal });
16
+ if (!opened.ok)
17
+ return err(opened.error);
18
+ }
19
+ return analysis.execute(operation, parameters, executionOptions);
20
+ },
21
+ };
22
+ registerEvidenceTools(server, targetAwareAnalysis, MANAGED_TOOL_CONTRACTS, options);
6
23
  };