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.
- package/README.md +14 -4
- package/bridge/ghidra/ReaGhidraBridge.java +477 -4
- package/bridge/hopper_bridge.py +54 -4
- package/dist/application/AnalysisContextQueries.js +130 -0
- package/dist/application/AnalysisSnapshotCache.js +4 -2
- package/dist/application/ArtifactInventory/scanCanonical.js +1 -1
- package/dist/application/BinarySession.js +5 -4
- package/dist/application/BinarySessionComposition.js +21 -0
- package/dist/application/BinarySessionRecords.js +79 -9
- package/dist/application/CapabilityInventory.js +16 -2
- package/dist/application/ClientRegistrationStatus.js +4 -1
- package/dist/application/DirectAnalysis.js +6 -2
- package/dist/application/EnhancedTools.js +37 -7
- package/dist/application/EvidenceLedger.js +45 -16
- package/dist/application/JavaScriptArtifactAnalysis.js +13 -4
- package/dist/application/LazyAnalysisProvider.js +77 -0
- package/dist/application/LoopbackReplay.js +1 -1
- package/dist/application/NativeApiInspection.js +80 -0
- package/dist/application/SessionProviderRouter.js +6 -6
- package/dist/application/SetupClientConfiguration.js +4 -2
- package/dist/application/runtime.js +26 -9
- package/dist/browser/PlaywrightBrowserScenarioProvider.js +8 -2
- package/dist/catalogIdentity.js +3 -3
- package/dist/cli/artifactCommands.js +5 -3
- package/dist/cli/coreAnalysisCommands.js +41 -0
- package/dist/cliCommandNames.js +1 -0
- package/dist/contracts/artifactToolContracts.js +8 -3
- package/dist/contracts/enhancedInputs.js +10 -0
- package/dist/contracts/functionWorkflowToolContracts.js +30 -0
- package/dist/contracts/promptContracts.js +14 -15
- package/dist/contracts/toolContractExamples.js +3 -0
- package/dist/contracts/toolContracts.js +21 -4
- package/dist/contracts/toolEffects.js +4 -0
- package/dist/contracts/toolOutputSchemaGroups.js +50 -10
- package/dist/domain/analysisErrorPresentation.js +8 -2
- package/dist/domain/analysisErrorProjection.js +1 -0
- package/dist/domain/bytecodeProvider.js +179 -0
- package/dist/domain/conformancePackage.js +169 -0
- package/dist/domain/conformanceReplay.js +104 -0
- package/dist/domain/conformanceTrustGate.js +193 -0
- package/dist/domain/customProtocolCapture.js +137 -0
- package/dist/domain/eventProcessTree.js +214 -0
- package/dist/domain/evidenceBundle.js +47 -0
- package/dist/domain/hopperStartupFailure.js +1 -1
- package/dist/domain/hopperValues.js +4 -1
- package/dist/domain/inputIssueProjection.js +7 -1
- package/dist/domain/javascriptSemanticAnalysis.js +8 -12
- package/dist/domain/javascriptSourceParser.js +15 -0
- package/dist/domain/javascriptStaticAnalysis.js +8 -12
- package/dist/domain/mobileApplicationInvestigation.js +159 -0
- package/dist/domain/nativeApiBoundary.js +130 -0
- package/dist/domain/objcSwiftMetadata.js +150 -0
- package/dist/domain/packageAnalysis.js +123 -0
- package/dist/domain/peInspection.js +196 -0
- package/dist/domain/protocolCapture.js +243 -0
- package/dist/dotnet/ManagedStaticProvider.js +34 -5
- package/dist/generatedMcpToolCatalog.js +37 -0
- package/dist/generatedPackageMetadata.js +4 -4
- package/dist/ghidra/GhidraDefaults.js +1 -1
- package/dist/ghidra/GhidraFunctionValues.js +27 -1
- package/dist/ghidra/GhidraProviderCapabilities.js +40 -38
- package/dist/hopper/HopperClient.js +26 -2
- package/dist/hopper/HopperProvider.js +42 -30
- package/dist/hopper/HopperRequestQueue.js +32 -4
- package/dist/hopper/HopperResponseStream.js +2 -2
- package/dist/hopper/protocol.js +63 -21
- package/dist/main/transport.js +2 -5
- package/dist/main.js +14 -12
- package/dist/mcpDoctor.js +2 -2
- package/dist/mcpStartupPolicy.js +14 -0
- package/dist/server/LazyToolCatalog.js +134 -0
- package/dist/server/createServer.js +64 -171
- package/dist/server/hydrateServerTools.js +191 -0
- package/dist/server/registerEnhancedTools.js +13 -3
- package/dist/server/registerEvidenceResources.js +50 -101
- package/dist/server/registerInvestigationTools.js +1 -2
- package/dist/server/registerJavaScriptApplicationGraphResource.js +0 -4
- package/dist/server/registerReconstructionObligationLedgerResource.js +0 -7
- package/dist/server/registerReconstructionReadinessResource.js +0 -7
- package/dist/server/registerSessionRecordTools.js +82 -12
- package/dist/server/registerSessionStatusTool.js +1 -1
- package/dist/server/registerSessionTools.js +21 -1
- package/dist/server/toolRegistrationOptions.js +21 -6
- package/dist/server/toolResult.js +12 -0
- package/package.json +21 -10
- package/scripts/package-runner-bootstrap.mjs +72 -0
- package/scripts/rea.mjs +11 -1
- package/skills/reverse-engineer-anything/SKILL.md +10 -3
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { AnalysisOutputError } from "../domain/errors.js";
|
|
2
|
+
import { err, ok } from "../domain/result.js";
|
|
3
|
+
const parameters = (document, address) => ({
|
|
4
|
+
...(document === undefined ? {} : { document }),
|
|
5
|
+
...(address === undefined ? {} : { address }),
|
|
6
|
+
});
|
|
7
|
+
const executionOptions = (signal) => signal === undefined ? {} : { signal };
|
|
8
|
+
/** Compose the provider's volatile selection state into one coherent query. */
|
|
9
|
+
export const getNavigationContext = async (analysis, input, signal) => {
|
|
10
|
+
const document = input.document ??
|
|
11
|
+
(await analysis.execute("current_document", {}, executionOptions(signal)));
|
|
12
|
+
if (typeof document !== "string" && !document.ok)
|
|
13
|
+
return document;
|
|
14
|
+
const documentName = typeof document === "string" ? document : document.value.result;
|
|
15
|
+
const documentArgs = typeof documentName === "string" ? parameters(documentName) : {};
|
|
16
|
+
const address = await analysis.execute("current_address", documentArgs, executionOptions(signal));
|
|
17
|
+
if (!address.ok)
|
|
18
|
+
return address;
|
|
19
|
+
if (typeof address.value.result !== "string")
|
|
20
|
+
return ok({
|
|
21
|
+
document: documentName,
|
|
22
|
+
address: address.value.result,
|
|
23
|
+
procedure: null,
|
|
24
|
+
});
|
|
25
|
+
const procedure = await analysis.execute("resolve_containing_procedure", parameters(typeof documentName === "string" ? documentName : undefined, address.value.result), executionOptions(signal));
|
|
26
|
+
if (!procedure.ok) {
|
|
27
|
+
if (!isMissingCurrentProcedure(procedure.error))
|
|
28
|
+
return procedure;
|
|
29
|
+
return ok({
|
|
30
|
+
document: documentName,
|
|
31
|
+
address: address.value.result,
|
|
32
|
+
procedure: null,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return ok({
|
|
36
|
+
document: documentName,
|
|
37
|
+
address: address.value.result,
|
|
38
|
+
procedure: containingProcedureName(procedure.value.result),
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
/** Inspect bounded provider-neutral facets at one explicit reproducible address. */
|
|
42
|
+
export const inspectAddressContext = async (analysis, input, signal) => {
|
|
43
|
+
const document = await resolveDocument(analysis, input.document, signal);
|
|
44
|
+
if (!document.ok)
|
|
45
|
+
return document;
|
|
46
|
+
const args = parameters(document.value, input.address);
|
|
47
|
+
const operations = [
|
|
48
|
+
"address_name",
|
|
49
|
+
"resolve_containing_procedure",
|
|
50
|
+
"comment",
|
|
51
|
+
"inline_comment",
|
|
52
|
+
"list_bookmarks",
|
|
53
|
+
];
|
|
54
|
+
const results = await Promise.all(operations.map((operation) => analysis.execute(operation, operation === "list_bookmarks" ? parameters(document.value) : args, executionOptions(signal))));
|
|
55
|
+
for (const result of results) {
|
|
56
|
+
if (!result.ok && isRequestLevelFailure(result.error))
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
const name = facet(results[0], operations[0]);
|
|
60
|
+
const procedure = facet(results[1], operations[1]);
|
|
61
|
+
const comment = facet(results[2], operations[2]);
|
|
62
|
+
const inlineComment = facet(results[3], operations[3]);
|
|
63
|
+
const bookmarks = facet(results[4], operations[4]);
|
|
64
|
+
return ok({
|
|
65
|
+
address: input.address,
|
|
66
|
+
document: document.value,
|
|
67
|
+
name,
|
|
68
|
+
procedure,
|
|
69
|
+
comment,
|
|
70
|
+
inline_comment: inlineComment,
|
|
71
|
+
bookmarks: bookmarks.state === "available"
|
|
72
|
+
? {
|
|
73
|
+
state: "available",
|
|
74
|
+
value: matchingBookmarks(bookmarks.value, resolvedAddress(results[1], input.address)),
|
|
75
|
+
}
|
|
76
|
+
: bookmarks,
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
const resolveDocument = async (analysis, document, signal) => {
|
|
80
|
+
if (document !== undefined)
|
|
81
|
+
return ok(document);
|
|
82
|
+
const current = await analysis.execute("current_document", {}, executionOptions(signal));
|
|
83
|
+
if (!current.ok)
|
|
84
|
+
return current;
|
|
85
|
+
return typeof current.value.result === "string"
|
|
86
|
+
? ok(current.value.result)
|
|
87
|
+
: err(new AnalysisOutputError("current_document", "expected a document identity string"));
|
|
88
|
+
};
|
|
89
|
+
const facet = (result, operation) => result?.ok === true
|
|
90
|
+
? { state: "available", value: result.value.result }
|
|
91
|
+
: {
|
|
92
|
+
state: "unavailable",
|
|
93
|
+
reason: result?.error.message ?? `${operation} returned no result`,
|
|
94
|
+
remediation: `Call ${operation} directly to inspect provider availability and diagnostics.`,
|
|
95
|
+
};
|
|
96
|
+
const isMissingCurrentProcedure = (error) => /(?:no procedure exists|not in a procedure)/iu.test(error.message);
|
|
97
|
+
const isRequestLevelFailure = (error) => error._tag === "AnalysisCancelledError" ||
|
|
98
|
+
error._tag === "AnalysisTimeoutError" ||
|
|
99
|
+
error._tag === "NoBinaryOpenError" ||
|
|
100
|
+
error._tag === "PermissionRequiredError";
|
|
101
|
+
const containingProcedureName = (value) => {
|
|
102
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
103
|
+
return null;
|
|
104
|
+
if (value.found !== true)
|
|
105
|
+
return null;
|
|
106
|
+
const procedure = value.procedure;
|
|
107
|
+
if (typeof procedure !== "object" ||
|
|
108
|
+
procedure === null ||
|
|
109
|
+
Array.isArray(procedure))
|
|
110
|
+
return null;
|
|
111
|
+
return typeof procedure.name === "string" ? procedure.name : null;
|
|
112
|
+
};
|
|
113
|
+
const resolvedAddress = (result, fallback) => {
|
|
114
|
+
if (result?.ok !== true)
|
|
115
|
+
return fallback;
|
|
116
|
+
const value = result.value.result;
|
|
117
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
118
|
+
return fallback;
|
|
119
|
+
return typeof value.query_address === "string"
|
|
120
|
+
? value.query_address
|
|
121
|
+
: fallback;
|
|
122
|
+
};
|
|
123
|
+
const matchingBookmarks = (value, address) => {
|
|
124
|
+
if (!Array.isArray(value))
|
|
125
|
+
return [];
|
|
126
|
+
return value.filter((bookmark) => typeof bookmark === "object" &&
|
|
127
|
+
bookmark !== null &&
|
|
128
|
+
!Array.isArray(bookmark) &&
|
|
129
|
+
bookmark.address === address);
|
|
130
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { EvidenceIntegrityError, NoBinaryOpenError, } from "../domain/errors.js";
|
|
2
2
|
import { err, ok } from "../domain/result.js";
|
|
3
3
|
import { analysisQueryId, createAnalysisSnapshotEntry, snapshotBinding, snapshotMatchesProfile, snapshotMatchesTarget, snapshotTarget, } from "../domain/analysisSnapshot.js";
|
|
4
|
-
import {
|
|
4
|
+
import { GENERATED_MCP_TOOL_CATALOG } from "../generatedMcpToolCatalog.js";
|
|
5
5
|
const STATEFUL_OPERATIONS = new Set([
|
|
6
6
|
"health",
|
|
7
7
|
"current_address",
|
|
@@ -19,7 +19,9 @@ const CURSOR_DEFAULT_OPERATIONS = new Set([
|
|
|
19
19
|
"prev_address",
|
|
20
20
|
"xrefs",
|
|
21
21
|
]);
|
|
22
|
-
const DOCUMENT_SCOPED_OPERATIONS = new Set(
|
|
22
|
+
const DOCUMENT_SCOPED_OPERATIONS = new Set(GENERATED_MCP_TOOL_CATALOG.filter(({ kind }) => kind === "official-proxy")
|
|
23
|
+
.map(({ name }) => name)
|
|
24
|
+
.filter((name) => name !== "current_document" &&
|
|
23
25
|
name !== "list_documents" &&
|
|
24
26
|
name !== "set_current_document"));
|
|
25
27
|
/** Whether an operation is immutable and independent of provider UI state. */
|
|
@@ -12,7 +12,7 @@ export const scanCanonicalArtifactInventory = async (path, limits, options = {})
|
|
|
12
12
|
const integrity = options.integrity ?? STRICT_INTEGRITY_POLICY;
|
|
13
13
|
if (integrity.mode === "record-and-continue" &&
|
|
14
14
|
(!integrity.approved || !integrity.enabled))
|
|
15
|
-
throw new ArtifactReaderFailure("
|
|
15
|
+
throw new ArtifactReaderFailure("policy", "Integrity continuation requires explicit approval and operator policy");
|
|
16
16
|
const metadata = await lstat(path);
|
|
17
17
|
const rootFormat = await classifyRoot(path, metadata.isDirectory());
|
|
18
18
|
const rootDigest = metadata.isDirectory()
|
|
@@ -2,15 +2,15 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { analysisProfilesEqual, } from "../domain/analysisProfile.js";
|
|
3
3
|
import { AnalysisCancelledError, ProviderAdapterError, } from "../domain/errors.js";
|
|
4
4
|
import { err, ok } from "../domain/result.js";
|
|
5
|
-
import {
|
|
6
|
-
import { OFFICIAL_TOOL_CONTRACTS } from "../contracts/toolContracts.js";
|
|
5
|
+
import { GENERATED_MCP_TOOL_CATALOG } from "../generatedMcpToolCatalog.js";
|
|
7
6
|
import { SessionProviderRouter, } from "./SessionProviderRouter.js";
|
|
8
7
|
import { BinarySessionRecords } from "./BinarySessionRecords.js";
|
|
9
8
|
import { binarySessionStatus } from "./BinarySessionStatus.js";
|
|
10
9
|
import { resolveSessionOpen, } from "./BinarySessionOpen.js";
|
|
11
10
|
import { prepareSessionExecution } from "./BinarySessionExecution.js";
|
|
12
11
|
import { closeAnalysisClient } from "./AnalysisClientCleanup.js";
|
|
13
|
-
const OFFICIAL_OPERATIONS = new Set(
|
|
12
|
+
const OFFICIAL_OPERATIONS = new Set(GENERATED_MCP_TOOL_CATALOG.filter(({ kind }) => kind === "official-proxy").map(({ name }) => name));
|
|
13
|
+
const ENHANCED_OPERATIONS = new Set(GENERATED_MCP_TOOL_CATALOG.filter(({ kind }) => kind === "enhanced").map(({ name }) => name));
|
|
14
14
|
/**
|
|
15
15
|
* Owns the single active target shared by CLI and MCP adapters.
|
|
16
16
|
*
|
|
@@ -42,7 +42,7 @@ export class BinarySession extends BinarySessionRecords {
|
|
|
42
42
|
const exact = route.capabilities?.get(operation)?.provider;
|
|
43
43
|
if (exact !== undefined)
|
|
44
44
|
selected = exact;
|
|
45
|
-
else if (
|
|
45
|
+
else if (ENHANCED_OPERATIONS.has(operation)) {
|
|
46
46
|
const providers = new Map();
|
|
47
47
|
for (const descriptor of route.capabilities?.values() ?? [])
|
|
48
48
|
if (descriptor.available &&
|
|
@@ -112,6 +112,7 @@ export class BinarySession extends BinarySessionRecords {
|
|
|
112
112
|
if (!imported.ok)
|
|
113
113
|
return imported;
|
|
114
114
|
}
|
|
115
|
+
this.resetSnapshotInvalidation();
|
|
115
116
|
return ok(target);
|
|
116
117
|
}
|
|
117
118
|
await this.#drainCalls();
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { AnalysisProviderRegistry } from "./AnalysisProviderRegistry.js";
|
|
2
|
+
import { BinarySession } from "./BinarySession.js";
|
|
3
|
+
import { SessionProviderRouter } from "./SessionProviderRouter.js";
|
|
4
|
+
const normalizeBinarySessionOptions = (optionsOrResolver) => typeof optionsOrResolver === "function"
|
|
5
|
+
? { resolveAnalysisProfile: optionsOrResolver }
|
|
6
|
+
: (optionsOrResolver ?? {});
|
|
7
|
+
/**
|
|
8
|
+
* Compose one provider-neutral session from deep-provider selection and
|
|
9
|
+
* disjoint auxiliary operation families.
|
|
10
|
+
*/
|
|
11
|
+
export const composeBinarySession = (registry, auxiliaryProviders = []) => new BinarySession(registry instanceof SessionProviderRouter
|
|
12
|
+
? registry
|
|
13
|
+
: SessionProviderRouter.selectable(registry, auxiliaryProviders));
|
|
14
|
+
/**
|
|
15
|
+
* Compose a session through the same internal seam for focused adapter tests.
|
|
16
|
+
* The constructor remains private to this composition module, so callers test
|
|
17
|
+
* provider behavior without coupling to BinarySession construction.
|
|
18
|
+
*/
|
|
19
|
+
export const composeBinarySessionFromFactory = (provider, optionsOrResolver) => new BinarySession(provider, normalizeBinarySessionOptions(optionsOrResolver));
|
|
20
|
+
/** Compose a focused session from any provider-shaped test seam. */
|
|
21
|
+
export const composeBinarySessionFromProvider = (provider, optionsOrResolver) => new BinarySession(provider, normalizeBinarySessionOptions(optionsOrResolver));
|
|
@@ -1,24 +1,37 @@
|
|
|
1
1
|
import { createEvidence } from "../domain/evidence.js";
|
|
2
|
-
import { evidenceBundleForTarget } from "../domain/evidenceBundle.js";
|
|
3
|
-
import { EvidenceIntegrityError, } from "../domain/errors.js";
|
|
2
|
+
import { evidenceBundleForTarget, serializeEvidenceBundle, } from "../domain/evidenceBundle.js";
|
|
3
|
+
import { EvidenceIntegrityError, EvidenceLimitError, } from "../domain/errors.js";
|
|
4
4
|
import { parseInvestigationWorkspace, } from "../domain/investigationWorkspace.js";
|
|
5
5
|
import { parseReconstructionCoverageWorkspace, } from "../domain/reconstructionCoverage.js";
|
|
6
|
-
import { err } from "../domain/result.js";
|
|
6
|
+
import { err, ok } from "../domain/result.js";
|
|
7
7
|
import { AnalysisSnapshotCache } from "./AnalysisSnapshotCache.js";
|
|
8
8
|
import { EvidenceLedger } from "./EvidenceLedger.js";
|
|
9
9
|
import { UNKNOWN_REGISTRY_PROVIDER, unknownEvidenceLinks, unknownMutationEvidence, } from "./UnknownEvidence.js";
|
|
10
10
|
/** Owns session evidence, snapshots, workspaces, and residual unknowns. */
|
|
11
11
|
export class BinarySessionRecords {
|
|
12
|
+
static MAX_RETAINED_BUNDLES = 16;
|
|
13
|
+
static MAX_RETAINED_BUNDLE_BYTES = 64 * 1024 * 1024;
|
|
12
14
|
#evidence = new EvidenceLedger({
|
|
13
15
|
maxRecords: 10_000,
|
|
14
16
|
maxBytes: 64 * 1024 * 1024,
|
|
15
17
|
});
|
|
16
18
|
#snapshot = new AnalysisSnapshotCache();
|
|
19
|
+
#retainedBundles = new Map();
|
|
20
|
+
#retainedBundleBytes = 0;
|
|
17
21
|
#investigationWorkspaces = new Map();
|
|
18
22
|
#coverageWorkspaces = new Map();
|
|
19
23
|
#snapshotInvalidated = false;
|
|
24
|
+
#snapshotListeners = new Set();
|
|
25
|
+
/** Observe changes to the mutable current analysis snapshot resource. */
|
|
26
|
+
onAnalysisSnapshotChanged(listener) {
|
|
27
|
+
this.#snapshotListeners.add(listener);
|
|
28
|
+
return () => this.#snapshotListeners.delete(listener);
|
|
29
|
+
}
|
|
20
30
|
recordEvidence(evidence) {
|
|
21
|
-
|
|
31
|
+
const recorded = this.#evidence.record(evidence);
|
|
32
|
+
if (recorded.ok && recorded.value === "added")
|
|
33
|
+
this.#emitSnapshotChanged();
|
|
34
|
+
return recorded;
|
|
22
35
|
}
|
|
23
36
|
hasEvidence(evidenceId) {
|
|
24
37
|
return this.#evidence.has(evidenceId);
|
|
@@ -29,8 +42,38 @@ export class BinarySessionRecords {
|
|
|
29
42
|
exportEvidenceBundle() {
|
|
30
43
|
return this.#evidence.export();
|
|
31
44
|
}
|
|
45
|
+
snapshotEvidenceBundle() {
|
|
46
|
+
const bundle = this.#evidence.export();
|
|
47
|
+
const encoded = serializeEvidenceBundle(bundle);
|
|
48
|
+
const digest = createHash("sha256").update(encoded).digest("hex");
|
|
49
|
+
const existing = this.#retainedBundles.get(digest);
|
|
50
|
+
if (existing === undefined) {
|
|
51
|
+
if (this.#retainedBundles.size >= BinarySessionRecords.MAX_RETAINED_BUNDLES)
|
|
52
|
+
return err(new EvidenceLimitError("records", BinarySessionRecords.MAX_RETAINED_BUNDLES));
|
|
53
|
+
const bytes = Buffer.byteLength(encoded);
|
|
54
|
+
if (this.#retainedBundleBytes + bytes >
|
|
55
|
+
BinarySessionRecords.MAX_RETAINED_BUNDLE_BYTES)
|
|
56
|
+
return err(new EvidenceLimitError("bytes", BinarySessionRecords.MAX_RETAINED_BUNDLE_BYTES));
|
|
57
|
+
this.#retainedBundles.set(digest, encoded);
|
|
58
|
+
this.#retainedBundleBytes += bytes;
|
|
59
|
+
}
|
|
60
|
+
return ok({
|
|
61
|
+
bundleDigest: digest,
|
|
62
|
+
bundleVersion: 2,
|
|
63
|
+
bytes: Buffer.byteLength(existing ?? encoded),
|
|
64
|
+
records: bundle.records.length,
|
|
65
|
+
unknowns: bundle.unknowns.length,
|
|
66
|
+
uri: `rea://evidence-bundle/${digest}`,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
retainedEvidenceBundle(digest) {
|
|
70
|
+
return this.#retainedBundles.get(digest);
|
|
71
|
+
}
|
|
32
72
|
importEvidenceBundle(bundle) {
|
|
33
|
-
|
|
73
|
+
const imported = this.#evidence.import(bundle);
|
|
74
|
+
if (imported.ok && imported.value > 0)
|
|
75
|
+
this.#emitSnapshotChanged();
|
|
76
|
+
return imported;
|
|
34
77
|
}
|
|
35
78
|
exportAnalysisSnapshot() {
|
|
36
79
|
const active = this.activeAnalysisBinding();
|
|
@@ -48,36 +91,53 @@ export class BinarySessionRecords {
|
|
|
48
91
|
const active = this.activeAnalysisBinding();
|
|
49
92
|
if (active?.profile === null)
|
|
50
93
|
return err(new EvidenceIntegrityError("Analysis snapshot profile_mismatch: the active target has no concrete analysis profile"));
|
|
51
|
-
|
|
94
|
+
const imported = this.#snapshot.import(snapshot, active === undefined
|
|
52
95
|
? undefined
|
|
53
96
|
: { target: active.target, profile: active.profile }, (bundle) => this.#evidence.import(bundle));
|
|
97
|
+
if (imported.ok)
|
|
98
|
+
this.#emitSnapshotChanged();
|
|
99
|
+
return imported;
|
|
54
100
|
}
|
|
55
101
|
matchesSnapshot(target, profile) {
|
|
56
102
|
return this.#snapshot.matches(target, profile ?? undefined);
|
|
57
103
|
}
|
|
58
104
|
selectSnapshot(target, profile) {
|
|
59
105
|
this.#snapshot.select(target, profile);
|
|
106
|
+
this.#emitSnapshotChanged();
|
|
60
107
|
}
|
|
61
108
|
lookupSnapshot(target, profile, operation, parameters) {
|
|
62
109
|
return this.#snapshot.lookup(target, profile, operation, parameters);
|
|
63
110
|
}
|
|
64
111
|
recordSnapshot(input) {
|
|
65
112
|
this.#snapshot.record(input);
|
|
113
|
+
this.#emitSnapshotChanged();
|
|
66
114
|
}
|
|
67
115
|
invalidateSnapshot() {
|
|
68
116
|
this.#snapshot.clear();
|
|
69
117
|
this.#snapshotInvalidated = true;
|
|
118
|
+
this.#emitSnapshotChanged();
|
|
70
119
|
}
|
|
71
120
|
resetSnapshotInvalidation() {
|
|
121
|
+
if (!this.#snapshotInvalidated)
|
|
122
|
+
return;
|
|
72
123
|
this.#snapshotInvalidated = false;
|
|
124
|
+
this.#emitSnapshotChanged();
|
|
73
125
|
}
|
|
74
126
|
clearSnapshot() {
|
|
75
127
|
this.#snapshot.clear();
|
|
128
|
+
this.#emitSnapshotChanged();
|
|
76
129
|
}
|
|
77
130
|
clearSessionRecords() {
|
|
78
131
|
this.#evidence.clear();
|
|
79
132
|
this.#snapshot.clear();
|
|
80
133
|
this.#snapshotInvalidated = false;
|
|
134
|
+
this.#retainedBundles.clear();
|
|
135
|
+
this.#retainedBundleBytes = 0;
|
|
136
|
+
this.#emitSnapshotChanged();
|
|
137
|
+
}
|
|
138
|
+
#emitSnapshotChanged() {
|
|
139
|
+
for (const listener of this.#snapshotListeners)
|
|
140
|
+
void listener();
|
|
81
141
|
}
|
|
82
142
|
retainInvestigationWorkspace(workspace) {
|
|
83
143
|
const parsed = parseInvestigationWorkspace(workspace);
|
|
@@ -111,10 +171,16 @@ export class BinarySessionRecords {
|
|
|
111
171
|
}
|
|
112
172
|
recordUnknown(input) {
|
|
113
173
|
const target = this.activeAnalysisBinding()?.target;
|
|
114
|
-
|
|
174
|
+
const recorded = this.#evidence.recordUnknown(input, unknownMutationEvidence(target, input));
|
|
175
|
+
if (recorded.ok)
|
|
176
|
+
this.#emitSnapshotChanged();
|
|
177
|
+
return recorded;
|
|
115
178
|
}
|
|
116
179
|
recordEvidenceWithUnknown(evidence, input) {
|
|
117
|
-
|
|
180
|
+
const recorded = this.#evidence.recordWithUnknown(evidence, input, unknownMutationEvidence(undefined, input));
|
|
181
|
+
if (recorded.ok)
|
|
182
|
+
this.#emitSnapshotChanged();
|
|
183
|
+
return recorded;
|
|
118
184
|
}
|
|
119
185
|
updateUnknown(input) {
|
|
120
186
|
const target = this.activeAnalysisBinding()?.target;
|
|
@@ -133,7 +199,10 @@ export class BinarySessionRecords {
|
|
|
133
199
|
"Registry mutation evidence records analyst intent, not proof of the answer.",
|
|
134
200
|
],
|
|
135
201
|
});
|
|
136
|
-
|
|
202
|
+
const updated = this.#evidence.updateUnknown(input, evidence);
|
|
203
|
+
if (updated.ok)
|
|
204
|
+
this.#emitSnapshotChanged();
|
|
205
|
+
return updated;
|
|
137
206
|
}
|
|
138
207
|
listUnknowns(filters = {}) {
|
|
139
208
|
return this.#evidence.listUnknowns(filters);
|
|
@@ -147,3 +216,4 @@ const sortedWorkspaces = (workspaces) => [...workspaces]
|
|
|
147
216
|
.sort((left, right) => left.workspace_id.localeCompare(right.workspace_id) ||
|
|
148
217
|
left.revision - right.revision)
|
|
149
218
|
.map((workspace) => structuredClone(workspace));
|
|
219
|
+
import { createHash } from "node:crypto";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import { GENERATED_MCP_TOOL_CATALOG } from "../generatedMcpToolCatalog.js";
|
|
3
3
|
const statusSchema = z.object({
|
|
4
4
|
open: z.boolean(),
|
|
5
5
|
kind: z.enum(["executable", "database", "archive", "artifact"]).optional(),
|
|
@@ -38,6 +38,7 @@ const ENHANCED_REQUIREMENTS = {
|
|
|
38
38
|
"list_strings",
|
|
39
39
|
],
|
|
40
40
|
analyze_function: ["analyze_function"],
|
|
41
|
+
inspect_native_api: ["analyze_function"],
|
|
41
42
|
trace_feature: [
|
|
42
43
|
"list_strings",
|
|
43
44
|
"list_procedures",
|
|
@@ -45,6 +46,13 @@ const ENHANCED_REQUIREMENTS = {
|
|
|
45
46
|
"resolve_containing_procedure",
|
|
46
47
|
],
|
|
47
48
|
};
|
|
49
|
+
const SESSION_COMPOSITION_REQUIREMENTS = {
|
|
50
|
+
get_navigation_context: [
|
|
51
|
+
"current_document",
|
|
52
|
+
"current_address",
|
|
53
|
+
"resolve_containing_procedure",
|
|
54
|
+
],
|
|
55
|
+
};
|
|
48
56
|
/** Build stable per-operation availability for discovery and tool visibility. */
|
|
49
57
|
export const buildCapabilityInventory = (sessionStatus, policy, clientFeatures = NO_CLIENT_FEATURES) => {
|
|
50
58
|
const status = statusSchema.parse(sessionStatus);
|
|
@@ -56,7 +64,7 @@ export const buildCapabilityInventory = (sessionStatus, policy, clientFeatures =
|
|
|
56
64
|
availability_code: descriptor.availability_code ?? null,
|
|
57
65
|
},
|
|
58
66
|
]));
|
|
59
|
-
return
|
|
67
|
+
return GENERATED_MCP_TOOL_CATALOG.map((contract) => {
|
|
60
68
|
const availability = availabilityFor({
|
|
61
69
|
name: contract.name,
|
|
62
70
|
kind: contract.kind,
|
|
@@ -101,6 +109,9 @@ const clientRequirementsFor = (name, clientFeatures) => {
|
|
|
101
109
|
};
|
|
102
110
|
};
|
|
103
111
|
const availabilityFor = (context) => {
|
|
112
|
+
const sessionComposition = SESSION_COMPOSITION_REQUIREMENTS[context.name];
|
|
113
|
+
if (sessionComposition !== undefined)
|
|
114
|
+
return composedAvailabilityFor(sessionComposition, context.descriptors);
|
|
104
115
|
const javascriptApplication = javascriptApplicationAvailability(context);
|
|
105
116
|
if (javascriptApplication !== null)
|
|
106
117
|
return javascriptApplication;
|
|
@@ -240,6 +251,9 @@ const composedAvailability = (name, descriptors) => {
|
|
|
240
251
|
reason: "provider_missing",
|
|
241
252
|
remediation: "No provider composition is declared for this operation.",
|
|
242
253
|
};
|
|
254
|
+
return composedAvailabilityFor(requirements, descriptors);
|
|
255
|
+
};
|
|
256
|
+
const composedAvailabilityFor = (requirements, descriptors) => {
|
|
243
257
|
const missing = requirements.find((operation) => !descriptors.has(operation));
|
|
244
258
|
if (missing !== undefined)
|
|
245
259
|
return {
|
|
@@ -3,6 +3,7 @@ import { resolve } from "node:path";
|
|
|
3
3
|
import { parse as parseToml } from "smol-toml";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { PRODUCT_IDENTITY } from "../identity.js";
|
|
6
|
+
import { MCP_STARTUP_POLICY } from "../mcpStartupPolicy.js";
|
|
6
7
|
import { supportedClients } from "./SupportedClients.js";
|
|
7
8
|
const objectSchema = z.record(z.string(), z.unknown());
|
|
8
9
|
const registrationSchema = z
|
|
@@ -41,7 +42,9 @@ export const readClientRegistrationStatuses = async (home, currentCommandPath =
|
|
|
41
42
|
};
|
|
42
43
|
const registrationAligned = (registration, client, currentCommandPath) => {
|
|
43
44
|
const command = [registration.command, ...registration.args];
|
|
44
|
-
if (client === "codex" &&
|
|
45
|
+
if (client === "codex" &&
|
|
46
|
+
registration.startup_timeout_sec !==
|
|
47
|
+
MCP_STARTUP_POLICY.codexStartupTimeoutSeconds)
|
|
45
48
|
return false;
|
|
46
49
|
if (command.length === 4 &&
|
|
47
50
|
command[0] === "npx" &&
|
|
@@ -182,6 +182,7 @@ const executeAnalysisTool = async (input) => {
|
|
|
182
182
|
const { session, tool, signal, evidenceProfile } = input;
|
|
183
183
|
if (tool === "binary_overview" ||
|
|
184
184
|
tool === "analyze_function" ||
|
|
185
|
+
tool === "inspect_native_api" ||
|
|
185
186
|
tool === "trace_feature") {
|
|
186
187
|
const result = await new EnhancedTools(session).execute(tool, input.arguments, signal);
|
|
187
188
|
if (!result.ok)
|
|
@@ -247,15 +248,18 @@ const prepareSnapshot = async (options) => {
|
|
|
247
248
|
return err(new EvidenceIntegrityError("Analysis snapshot target does not match the requested binary"));
|
|
248
249
|
return ok({ snapshot: loaded.value });
|
|
249
250
|
};
|
|
250
|
-
const replayProviderFor = (session, tool) => tool
|
|
251
|
+
const replayProviderFor = (session, tool) => isWorkflowEvidenceTool(tool)
|
|
251
252
|
? REA_WORKFLOW_PROVIDER
|
|
252
253
|
: session.providerIdentity(tool);
|
|
253
254
|
const analysisProfileForEvidence = (session, tool) => {
|
|
254
|
-
if (tool
|
|
255
|
+
if (!isWorkflowEvidenceTool(tool))
|
|
255
256
|
return session.analysisProfile(tool);
|
|
256
257
|
const upstream = session.analysisProfile();
|
|
257
258
|
return upstream === undefined ? undefined : workflowAnalysisProfile(upstream);
|
|
258
259
|
};
|
|
260
|
+
const isWorkflowEvidenceTool = (tool) => tool === "binary_overview" ||
|
|
261
|
+
tool === "inspect_native_api" ||
|
|
262
|
+
tool === "trace_feature";
|
|
259
263
|
const fileExists = async (path) => {
|
|
260
264
|
try {
|
|
261
265
|
await access(path);
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { enhancedInputSchemas } from "../contracts/enhancedInputs.js";
|
|
2
2
|
import { AnalysisCancelledError, AnalysisOutputError, projectAnalysisError, } from "../domain/errors.js";
|
|
3
|
-
import { addressDistance, parseDocuments, parseFunctionDossier, parseListCount, parseRelatedAddresses, parseSegments, } from "../domain/hopperValues.js";
|
|
3
|
+
import { addressDistance, functionDossierSchema, parseDocuments, parseFunctionDossier, parseListCount, parseRelatedAddresses, parseSegments, } from "../domain/hopperValues.js";
|
|
4
4
|
import { err, ok } from "../domain/result.js";
|
|
5
5
|
import { categorizeSwiftTypes, discoverObjcClasses, discoverObjcProtocols, discoverSwiftClasses, } from "../domain/symbolAnalysis.js";
|
|
6
|
+
import { jsonValueSchema } from "../domain/jsonValue.js";
|
|
6
7
|
import { invalidEnhancedInput, } from "./EnhancedToolTypes.js";
|
|
7
8
|
import { readAllAddressed } from "./EnhancedToolPagination.js";
|
|
8
9
|
import { traceCallPath } from "./CallPathTracing.js";
|
|
9
10
|
import { traceLiteralFeature } from "./EnhancedLiteralTracing.js";
|
|
11
|
+
import { projectNativeApiInspection } from "./NativeApiInspection.js";
|
|
10
12
|
/**
|
|
11
13
|
* Composes direct provider operations into bounded reverse-engineering tools.
|
|
12
14
|
* Direct callers use `execute`; adapters that own validation use
|
|
@@ -23,6 +25,8 @@ export class EnhancedTools {
|
|
|
23
25
|
name === "find_code_for_string" ||
|
|
24
26
|
name === "trace_call_path")
|
|
25
27
|
return this.#executeTracing(name, input, signal);
|
|
28
|
+
if (name === "analyze_function" || name === "inspect_native_api")
|
|
29
|
+
return this.#executeFunctionAnalysis(name, input, signal);
|
|
26
30
|
switch (name) {
|
|
27
31
|
case "swift_classes": {
|
|
28
32
|
const parsed = enhancedInputSchemas.swift_classes.safeParse(input);
|
|
@@ -64,12 +68,6 @@ export class EnhancedTools {
|
|
|
64
68
|
? this.executeValidated({ name, input: parsed.data }, signal)
|
|
65
69
|
: invalidEnhancedInput(name, parsed.error);
|
|
66
70
|
}
|
|
67
|
-
case "analyze_function": {
|
|
68
|
-
const parsed = enhancedInputSchemas.analyze_function.safeParse(input);
|
|
69
|
-
return parsed.success
|
|
70
|
-
? this.executeValidated({ name, input: parsed.data }, signal)
|
|
71
|
-
: invalidEnhancedInput(name, parsed.error);
|
|
72
|
-
}
|
|
73
71
|
}
|
|
74
72
|
}
|
|
75
73
|
/** Dispatch input already parsed by a trusted adapter boundary. */
|
|
@@ -93,6 +91,8 @@ export class EnhancedTools {
|
|
|
93
91
|
return this.#binaryOverview(call.input, signal);
|
|
94
92
|
case "analyze_function":
|
|
95
93
|
return this.#analyzeFunction(call.input, signal);
|
|
94
|
+
case "inspect_native_api":
|
|
95
|
+
return this.#inspectNativeApi(call.input, signal);
|
|
96
96
|
case "trace_feature":
|
|
97
97
|
return traceLiteralFeature((name, arguments_, operationSignal) => this.#call(name, arguments_, operationSignal), call.input, "feature", signal);
|
|
98
98
|
case "find_code_for_string":
|
|
@@ -115,10 +115,40 @@ export class EnhancedTools {
|
|
|
115
115
|
input: enhancedInputSchemas[name].parse(parsed.data),
|
|
116
116
|
}, signal);
|
|
117
117
|
}
|
|
118
|
+
#executeFunctionAnalysis(name, input, signal) {
|
|
119
|
+
const parsed = enhancedInputSchemas[name].safeParse(input);
|
|
120
|
+
if (!parsed.success)
|
|
121
|
+
return invalidEnhancedInput(name, parsed.error);
|
|
122
|
+
if (name === "analyze_function")
|
|
123
|
+
return this.executeValidated({
|
|
124
|
+
name,
|
|
125
|
+
input: enhancedInputSchemas.analyze_function.parse(parsed.data),
|
|
126
|
+
}, signal);
|
|
127
|
+
return this.executeValidated({
|
|
128
|
+
name,
|
|
129
|
+
input: enhancedInputSchemas.inspect_native_api.parse(parsed.data),
|
|
130
|
+
}, signal);
|
|
131
|
+
}
|
|
118
132
|
async #analyzeFunction(input, signal) {
|
|
119
133
|
const result = await this.#call("analyze_function", input, signal);
|
|
120
134
|
return result.ok ? parseFunctionDossier(result.value) : result;
|
|
121
135
|
}
|
|
136
|
+
async #inspectNativeApi(input, signal) {
|
|
137
|
+
const analysisInput = enhancedInputSchemas.analyze_function.parse({
|
|
138
|
+
procedure: input.procedure,
|
|
139
|
+
include_assembly: true,
|
|
140
|
+
max_pseudocode_chars: input.max_pseudocode_chars,
|
|
141
|
+
max_instructions: input.max_instructions,
|
|
142
|
+
});
|
|
143
|
+
const result = await this.#call("analyze_function", analysisInput, signal);
|
|
144
|
+
if (!result.ok)
|
|
145
|
+
return result;
|
|
146
|
+
const parsed = parseFunctionDossier(result.value);
|
|
147
|
+
if (!parsed.ok)
|
|
148
|
+
return parsed;
|
|
149
|
+
const dossier = functionDossierSchema.parse(parsed.value);
|
|
150
|
+
return ok(jsonValueSchema.parse(projectNativeApiInspection(dossier)));
|
|
151
|
+
}
|
|
122
152
|
async #swiftClasses(pattern, signal) {
|
|
123
153
|
const procedures = await this.#allAddressed("list_procedures", signal);
|
|
124
154
|
return procedures.ok
|