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.
- package/README.md +29 -10
- package/dist/application/AndroidApplicationService.js +17 -0
- package/dist/application/AppleApplicationService.js +17 -0
- package/dist/application/ClientRegistrationStatus.js +11 -4
- package/dist/application/InventoryProjectionEvidence.js +41 -0
- package/dist/application/InvestigationProviders.js +12 -0
- package/dist/application/ProcessPairedExperiment.js +72 -0
- package/dist/application/Setup.js +3 -3
- package/dist/application/SetupClientConfiguration.js +10 -6
- package/dist/application/SetupPlan.js +3 -2
- package/dist/application/SetupSkill.js +44 -40
- package/dist/application/Uninstall.js +7 -1
- package/dist/application/Upgrade.js +16 -1
- package/dist/cli/setupCommands.js +8 -3
- package/dist/cliSetup.js +29 -12
- package/dist/contracts/electronToolContracts.js +51 -3
- package/dist/contracts/managedToolContracts.js +7 -2
- package/dist/contracts/sessionStatusContract.js +26 -0
- package/dist/contracts/toolContractTypes.js +12 -1
- package/dist/contracts/toolContracts.js +3 -10
- package/dist/contracts/toolOutputSchemaGroups.js +51 -9
- package/dist/contracts/toolOutputSchemaPrimitives.js +33 -32
- package/dist/domain/androidApplication.js +206 -0
- package/dist/domain/appleApplication.js +221 -0
- package/dist/domain/boundedCartesianProjection.js +15 -0
- package/dist/domain/processComparison.js +19 -0
- package/dist/domain/processPairedExperiment.js +128 -0
- package/dist/domain/replayMachine.js +316 -0
- package/dist/domain/replayMachineRuntime.js +215 -0
- package/dist/domain/runtimeIdentification.js +210 -0
- package/dist/evaluation/CodexAgentEval.js +132 -0
- package/dist/generatedPackageMetadata.js +2 -2
- package/dist/identity.js +2 -1
- package/dist/server/createServer.js +5 -2
- package/dist/server/javascriptApplicationResult.js +85 -0
- package/dist/server/registerElectronTools.js +19 -5
- package/dist/server/registerEvidenceResources.js +2 -0
- package/dist/server/registerJavaScriptApplicationGraphResource.js +69 -0
- package/dist/server/registerManagedTools.js +18 -1
- package/dist/server/registerSessionStatusTool.js +95 -25
- package/dist/server/toolResult.js +10 -4
- package/package.json +2 -1
- package/skills/reverse-engineer-anything/SKILL.md +68 -227
- package/skills/reverse-engineer-anything/references/controlled-replay.md +12 -0
- package/skills/reverse-engineer-anything/references/evidence-workflows.md +33 -0
- package/skills/reverse-engineer-anything/references/javascript-applications.md +25 -0
- package/skills/reverse-engineer-anything/references/native-and-artifacts.md +36 -0
- 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.
|
|
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: "
|
|
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}
|
|
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
|
-
?
|
|
49
|
-
:
|
|
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:
|
|
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
|
-
|
|
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
|
};
|