rea-agents 2.5.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/README.md +14 -4
  2. package/bridge/ghidra/ReaGhidraBridge.java +477 -4
  3. package/bridge/hopper_bridge.py +54 -4
  4. package/dist/application/AnalysisContextQueries.js +130 -0
  5. package/dist/application/AnalysisSnapshotCache.js +4 -2
  6. package/dist/application/ArtifactInventory/scanCanonical.js +1 -1
  7. package/dist/application/BinarySession.js +5 -4
  8. package/dist/application/BinarySessionComposition.js +21 -0
  9. package/dist/application/BinarySessionRecords.js +79 -9
  10. package/dist/application/CapabilityInventory.js +16 -2
  11. package/dist/application/ClientRegistrationStatus.js +4 -1
  12. package/dist/application/DirectAnalysis.js +6 -2
  13. package/dist/application/EnhancedTools.js +37 -7
  14. package/dist/application/EvidenceLedger.js +45 -16
  15. package/dist/application/JavaScriptArtifactAnalysis.js +13 -4
  16. package/dist/application/LazyAnalysisProvider.js +77 -0
  17. package/dist/application/LoopbackReplay.js +1 -1
  18. package/dist/application/NativeApiInspection.js +80 -0
  19. package/dist/application/SessionProviderRouter.js +6 -6
  20. package/dist/application/SetupClientConfiguration.js +4 -2
  21. package/dist/application/runtime.js +26 -9
  22. package/dist/browser/PlaywrightBrowserScenarioProvider.js +8 -2
  23. package/dist/catalogIdentity.js +3 -3
  24. package/dist/cli/artifactCommands.js +5 -3
  25. package/dist/cli/coreAnalysisCommands.js +41 -0
  26. package/dist/cliCommandNames.js +1 -0
  27. package/dist/contracts/artifactToolContracts.js +8 -3
  28. package/dist/contracts/enhancedInputs.js +10 -0
  29. package/dist/contracts/functionWorkflowToolContracts.js +30 -0
  30. package/dist/contracts/promptContracts.js +14 -15
  31. package/dist/contracts/toolContractExamples.js +3 -0
  32. package/dist/contracts/toolContracts.js +21 -4
  33. package/dist/contracts/toolEffects.js +4 -0
  34. package/dist/contracts/toolOutputSchemaGroups.js +50 -10
  35. package/dist/domain/analysisErrorPresentation.js +8 -2
  36. package/dist/domain/analysisErrorProjection.js +1 -0
  37. package/dist/domain/bytecodeProvider.js +179 -0
  38. package/dist/domain/conformancePackage.js +169 -0
  39. package/dist/domain/conformanceReplay.js +104 -0
  40. package/dist/domain/conformanceTrustGate.js +193 -0
  41. package/dist/domain/customProtocolCapture.js +137 -0
  42. package/dist/domain/eventProcessTree.js +214 -0
  43. package/dist/domain/evidenceBundle.js +47 -0
  44. package/dist/domain/hopperStartupFailure.js +1 -1
  45. package/dist/domain/hopperValues.js +4 -1
  46. package/dist/domain/inputIssueProjection.js +7 -1
  47. package/dist/domain/javascriptSemanticAnalysis.js +8 -12
  48. package/dist/domain/javascriptSourceParser.js +15 -0
  49. package/dist/domain/javascriptStaticAnalysis.js +8 -12
  50. package/dist/domain/mobileApplicationInvestigation.js +159 -0
  51. package/dist/domain/nativeApiBoundary.js +130 -0
  52. package/dist/domain/objcSwiftMetadata.js +150 -0
  53. package/dist/domain/packageAnalysis.js +123 -0
  54. package/dist/domain/peInspection.js +196 -0
  55. package/dist/domain/protocolCapture.js +243 -0
  56. package/dist/dotnet/ManagedStaticProvider.js +34 -5
  57. package/dist/generatedMcpToolCatalog.js +37 -0
  58. package/dist/generatedPackageMetadata.js +4 -4
  59. package/dist/ghidra/GhidraDefaults.js +1 -1
  60. package/dist/ghidra/GhidraFunctionValues.js +27 -1
  61. package/dist/ghidra/GhidraProviderCapabilities.js +40 -38
  62. package/dist/hopper/HopperClient.js +26 -2
  63. package/dist/hopper/HopperProvider.js +42 -30
  64. package/dist/hopper/HopperRequestQueue.js +32 -4
  65. package/dist/hopper/HopperResponseStream.js +2 -2
  66. package/dist/hopper/protocol.js +63 -21
  67. package/dist/main/transport.js +2 -5
  68. package/dist/main.js +14 -12
  69. package/dist/mcpDoctor.js +2 -2
  70. package/dist/mcpStartupPolicy.js +14 -0
  71. package/dist/server/LazyToolCatalog.js +134 -0
  72. package/dist/server/createServer.js +64 -171
  73. package/dist/server/hydrateServerTools.js +191 -0
  74. package/dist/server/registerEnhancedTools.js +13 -3
  75. package/dist/server/registerEvidenceResources.js +50 -101
  76. package/dist/server/registerInvestigationTools.js +1 -2
  77. package/dist/server/registerJavaScriptApplicationGraphResource.js +0 -4
  78. package/dist/server/registerReconstructionObligationLedgerResource.js +0 -7
  79. package/dist/server/registerReconstructionReadinessResource.js +0 -7
  80. package/dist/server/registerSessionRecordTools.js +82 -12
  81. package/dist/server/registerSessionStatusTool.js +1 -1
  82. package/dist/server/registerSessionTools.js +21 -1
  83. package/dist/server/toolRegistrationOptions.js +21 -6
  84. package/dist/server/toolResult.js +12 -0
  85. package/package.json +21 -10
  86. package/scripts/package-runner-bootstrap.mjs +72 -0
  87. package/scripts/rea.mjs +11 -1
  88. package/skills/reverse-engineer-anything/SKILL.md +10 -3
@@ -1,5 +1,5 @@
1
1
  import canonicalize from "canonicalize";
2
- import { createEvidenceBundle, parseEvidenceBundle, } from "../domain/evidenceBundle.js";
2
+ import { createEvidenceBundle, parseEvidenceBundle, validateResidualUnknownAddition, } from "../domain/evidenceBundle.js";
3
3
  import { EvidenceIntegrityError, EvidenceLimitError, UnknownRegistryError, } from "../domain/errors.js";
4
4
  import { parseEvidence } from "../domain/evidence.js";
5
5
  import { createResidualUnknown, updateResidualUnknown, } from "../domain/residualUnknown.js";
@@ -142,26 +142,19 @@ export class EvidenceLedger {
142
142
  catch (cause) {
143
143
  return err(new UnknownRegistryError("invalid-transition", { cause }));
144
144
  }
145
- const pendingRecords = new Map(this.#records);
146
- pendingRecords.set(evidence.evidence_id, evidence);
147
145
  const existingUnknown = this.#unknownHeads.get(unknown.unknown_id);
148
146
  if (existingUnknown !== undefined) {
149
147
  if (existingUnknown.revision !== 1 ||
150
148
  existingUnknown.revision_digest !== unknown.revision_digest)
151
149
  return err(new UnknownRegistryError("already-exists"));
152
- const checked = this.#validateCandidate(pendingRecords, this.#unknownRevisions);
150
+ const checked = this.#appendIncremental([evidence]);
153
151
  if (!checked.ok)
154
152
  return checked;
155
- this.#commit(pendingRecords, this.#unknownRevisions, checked.value);
156
153
  return ok(null);
157
154
  }
158
- pendingRecords.set(mutation.evidence_id, mutation);
159
- const pendingUnknowns = new Map(this.#unknownRevisions);
160
- pendingUnknowns.set(unknownRevisionKey(unknown), unknown);
161
- const checked = this.#validateCandidate(pendingRecords, pendingUnknowns);
155
+ const checked = this.#appendIncremental([evidence, mutation], unknown);
162
156
  if (!checked.ok)
163
157
  return checked;
164
- this.#commit(pendingRecords, pendingUnknowns, checked.value);
165
158
  return ok(structuredClone(unknown));
166
159
  }
167
160
  /** Apply one approved full-state update using optimistic revision matching. */
@@ -218,16 +211,52 @@ export class EvidenceLedger {
218
211
  cause,
219
212
  }));
220
213
  }
221
- const pendingRecords = new Map(this.#records);
222
- pendingRecords.set(mutationEvidence.evidence_id, mutationEvidence);
223
- const pendingUnknowns = new Map(this.#unknownRevisions);
224
- pendingUnknowns.set(unknownRevisionKey(unknown), unknown);
225
- const checked = this.#validateCandidate(pendingRecords, pendingUnknowns);
214
+ const checked = this.#appendIncremental([mutationEvidence], unknown);
226
215
  if (!checked.ok)
227
216
  return checked;
228
- this.#commit(pendingRecords, pendingUnknowns, checked.value);
229
217
  return ok(structuredClone(unknown));
230
218
  }
219
+ #appendIncremental(records, unknown) {
220
+ const additions = new Map();
221
+ let addedBytes = 0;
222
+ for (const evidence of records) {
223
+ const existing = additions.get(evidence.evidence_id) ??
224
+ this.#records.get(evidence.evidence_id);
225
+ if (existing !== undefined) {
226
+ if (!recordsAgree(existing, evidence))
227
+ return err(new EvidenceIntegrityError("Conflicting evidence record"));
228
+ continue;
229
+ }
230
+ additions.set(evidence.evidence_id, evidence);
231
+ addedBytes += serializedBytes(evidence);
232
+ }
233
+ const unknownKey = unknown === undefined ? undefined : unknownRevisionKey(unknown);
234
+ if (unknownKey !== undefined && this.#unknownRevisions.has(unknownKey))
235
+ return err(new UnknownRegistryError("integrity"));
236
+ const unknownBytes = unknown === undefined ? 0 : serializedBytes(unknown);
237
+ if (this.#exceedsRecordLimit(this.#records.size + additions.size, this.#unknownRevisions.size + (unknown === undefined ? 0 : 1)))
238
+ return err(new EvidenceLimitError("records", this.limits.maxRecords));
239
+ if (this.#bytes + addedBytes + unknownBytes > this.limits.maxBytes)
240
+ return err(new EvidenceLimitError("bytes", this.limits.maxBytes));
241
+ if (unknown !== undefined)
242
+ try {
243
+ validateResidualUnknownAddition(unknown, {
244
+ get: (id) => additions.get(id) ?? this.#records.get(id),
245
+ has: (id) => additions.has(id) || this.#records.has(id),
246
+ }, this.#unknownHeads);
247
+ }
248
+ catch (cause) {
249
+ return err(new UnknownRegistryError("integrity", { cause }));
250
+ }
251
+ for (const [id, evidence] of additions)
252
+ this.#records.set(id, evidence);
253
+ if (unknown !== undefined && unknownKey !== undefined) {
254
+ this.#unknownRevisions.set(unknownKey, unknown);
255
+ this.#unknownHeads.set(unknown.unknown_id, unknown);
256
+ }
257
+ this.#bytes += addedBytes + unknownBytes;
258
+ return ok(undefined);
259
+ }
231
260
  #validateCandidate(records, unknowns) {
232
261
  if (this.#exceedsRecordLimit(records.size, unknowns.size))
233
262
  return err(new EvidenceLimitError("records", this.limits.maxRecords));
@@ -1,7 +1,9 @@
1
1
  import { createHash } from "node:crypto";
2
- import { analyzeJavaScriptStaticSource } from "../domain/javascriptStaticAnalysis.js";
3
- import { analyzeJavaScriptSemantics } from "../domain/javascriptSemanticAnalysis.js";
2
+ import { analyzeParsedJavaScriptStaticSource } from "../domain/javascriptStaticAnalysis.js";
3
+ import { analyzeParsedJavaScriptSemantics } from "../domain/javascriptSemanticAnalysis.js";
4
4
  import { DEFAULT_JAVASCRIPT_SEMANTIC_LIMITS, } from "../domain/javascriptSemanticIr.js";
5
+ import { parseJavaScriptSource } from "../domain/javascriptSourceParser.js";
6
+ import { failedJavaScriptStaticAnalysis } from "../domain/javascriptStaticAnalysisHelpers.js";
5
7
  import { analyzeJavaScriptJsonModule } from "./JavaScriptJsonModules.js";
6
8
  /** Analyze selected text files under one shared AST, finding, module, and time budget. */
7
9
  export const analyzeJavaScriptArtifactFiles = (fileSet, input, now) => {
@@ -70,7 +72,14 @@ const analyzeArtifactFile = (file, context) => {
70
72
  state.truncatedScopes += 1;
71
73
  return;
72
74
  }
73
- const analysis = analyzeJavaScriptStaticSource(file.text.value, {
75
+ const parsed = parseJavaScriptSource(file.text.value);
76
+ if (parsed === null) {
77
+ const analysis = failedJavaScriptStaticAnalysis();
78
+ state.files.push({ file, javascript: analysis, semantic: null });
79
+ state.parseFailures += 1;
80
+ return;
81
+ }
82
+ const analysis = analyzeParsedJavaScriptStaticSource(file.text.value, parsed, {
74
83
  maxAstNodes: remainingNodes,
75
84
  maxFindings: remainingFindings,
76
85
  maxModules: remainingModules,
@@ -88,7 +97,7 @@ const analyzeArtifactFile = (file, context) => {
88
97
  maxModuleLinks: semanticFindingBudget,
89
98
  };
90
99
  const semantics = analysis.parse_status === "complete" || analysis.parse_status === "partial"
91
- ? analyzeJavaScriptSemantics(file.text.value, semanticLimits)
100
+ ? analyzeParsedJavaScriptSemantics(parsed, semanticLimits)
92
101
  : null;
93
102
  state.files.push({
94
103
  file,
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Preserve synchronous provider discovery while loading implementation modules
3
+ * only when a target-bound client first executes.
4
+ */
5
+ export class LazyAnalysisProvider {
6
+ #providerIdentity;
7
+ #capabilityDescriptors;
8
+ #loadProvider;
9
+ #provider;
10
+ constructor(options) {
11
+ this.#providerIdentity = options.identity;
12
+ this.#capabilityDescriptors = options.capabilities;
13
+ this.#loadProvider = options.load;
14
+ }
15
+ /** Return generated provider identity without loading its implementation. */
16
+ identity() {
17
+ return this.#providerIdentity;
18
+ }
19
+ /** Return generated capability metadata without loading its implementation. */
20
+ capabilities() {
21
+ return this.#capabilityDescriptors;
22
+ }
23
+ /** Create a client whose implementation is loaded on first execution. */
24
+ createClient(target, profile, context) {
25
+ return new LazyAnalysisClient(async () => {
26
+ const provider = await this.#load();
27
+ return provider.createClient(target, profile, context);
28
+ });
29
+ }
30
+ async #load() {
31
+ this.#provider ??= this.#loadProvider().catch((cause) => {
32
+ this.#provider = undefined;
33
+ throw cause;
34
+ });
35
+ return this.#provider;
36
+ }
37
+ }
38
+ class LazyAnalysisClient {
39
+ #loadClient;
40
+ #client;
41
+ #loading;
42
+ #closed = false;
43
+ constructor(loadClient) {
44
+ this.#loadClient = loadClient;
45
+ }
46
+ execute = async (operation, parameters, options) => {
47
+ const client = await this.#load();
48
+ return client.execute(operation, parameters, options);
49
+ };
50
+ runtimeLineageSnapshots() {
51
+ return this.#client?.runtimeLineageSnapshots?.() ?? [];
52
+ }
53
+ requestActivitySnapshots() {
54
+ return this.#client?.requestActivitySnapshots?.() ?? [];
55
+ }
56
+ async close() {
57
+ this.#closed = true;
58
+ if (this.#loading === undefined)
59
+ return;
60
+ const client = await this.#loading;
61
+ await client.close();
62
+ }
63
+ async #load() {
64
+ if (this.#closed)
65
+ throw new Error("Lazy analysis client is closed");
66
+ this.#loading ??= this.#loadClient()
67
+ .then((client) => {
68
+ this.#client = client;
69
+ return client;
70
+ })
71
+ .catch((cause) => {
72
+ this.#loading = undefined;
73
+ throw cause;
74
+ });
75
+ return this.#loading;
76
+ }
77
+ }
@@ -374,11 +374,11 @@ export const startLoopbackReplay = async (scenario, recordEvent = () => undefine
374
374
  async close() {
375
375
  closePromise ??= (async () => {
376
376
  recorder.stopMachineAdmission();
377
+ await recorder.drainMachine();
377
378
  for (const client of websocket.clients)
378
379
  client.terminate();
379
380
  await new Promise((resolveClose) => websocket.close(() => resolveClose()));
380
381
  await closeReplayServer(server);
381
- await recorder.drainMachine();
382
382
  })();
383
383
  await closePromise;
384
384
  },
@@ -0,0 +1,80 @@
1
+ import { nativeApiInspectionResultSchema, } from "../domain/nativeApiBoundary.js";
2
+ const unavailableBoundary = () => ({
3
+ available: false,
4
+ reason: "The selected provider did not expose a structured native API boundary model.",
5
+ residual_unknowns: [
6
+ "What are the function's return and parameter boundary types?",
7
+ "Does this function dispatch through a jump table with recoverable data addresses?",
8
+ ],
9
+ });
10
+ /** Project one analyzed-function dossier into inspectable native API substeps. */
11
+ export const projectNativeApiInspection = (dossier) => {
12
+ const boundary = dossier.native_api ?? unavailableBoundary();
13
+ const unsupportedBranches = boundary.available
14
+ ? []
15
+ : ["structured-boundary-types", "jump-table-data-mapping"];
16
+ const residualUnknowns = boundary.available
17
+ ? availableResidualUnknowns(boundary)
18
+ : boundary.residual_unknowns;
19
+ return nativeApiInspectionResultSchema.parse({
20
+ schema_version: 1,
21
+ procedure: {
22
+ address: dossier.procedure.address,
23
+ name: dossier.procedure.name,
24
+ },
25
+ boundary,
26
+ substeps: [
27
+ {
28
+ operation: "analyze_function",
29
+ status: "completed",
30
+ observations: [
31
+ "provider function dossier",
32
+ "provider pseudocode classification",
33
+ "provider control-flow observations",
34
+ ],
35
+ },
36
+ {
37
+ operation: "project_native_api_boundary",
38
+ status: boundary.available ? "completed" : "unsupported",
39
+ observations: boundary.available
40
+ ? [
41
+ "return and parameter boundary types",
42
+ "confidence and inference evidence",
43
+ "jump-table dispatch, data, and target addresses",
44
+ ]
45
+ : [boundary.reason],
46
+ },
47
+ {
48
+ operation: "preserve_residual_unknowns",
49
+ status: "completed",
50
+ observations: residualUnknowns.length === 0
51
+ ? ["no additional structured-boundary unknowns"]
52
+ : residualUnknowns,
53
+ },
54
+ ],
55
+ unsupported_branches: unsupportedBranches,
56
+ residual_unknowns: residualUnknowns,
57
+ });
58
+ };
59
+ const availableResidualUnknowns = (boundary) => {
60
+ const unknowns = [];
61
+ if (boundary.parameters_truncated)
62
+ unknowns.push("Which additional parameters were omitted by the native API boundary limit?");
63
+ if (boundary.jump_tables_truncated)
64
+ unknowns.push("Which additional jump tables were omitted by the native API boundary limit?");
65
+ for (const table of boundary.jump_tables) {
66
+ if (table.data_sources_truncated || table.mappings_truncated)
67
+ unknowns.push(`Which additional data sources or targets were omitted for the jump table dispatched at ${table.dispatch_address}?`);
68
+ if (table.data_sources.length === 0)
69
+ unknowns.push(`What backing data address produced the jump table dispatched at ${table.dispatch_address}?`);
70
+ if (table.data_sources.some(({ entry_count: count, entry_size_bytes: size }) => count === null || size === null))
71
+ unknowns.push(`What are the exact entry size and count for candidate jump-table data dispatched at ${table.dispatch_address}?`);
72
+ if (table.mappings.some(({ case_value: value }) => value === null))
73
+ unknowns.push(`Which source-level case values correspond to every target dispatched at ${table.dispatch_address}?`);
74
+ }
75
+ for (const value of [boundary.return_type, ...boundary.parameters]) {
76
+ if (value.confidence === "low")
77
+ unknowns.push(`Can the inferred ${value.role} type ${value.data_type} be confirmed with imported metadata or an ABI probe?`);
78
+ }
79
+ return [...new Set(unknowns)];
80
+ };
@@ -26,7 +26,7 @@ export class SessionProviderRouter {
26
26
  : (target, resolutionOptions) => provider.resolveAnalysisProfile?.(target, resolutionOptions) ??
27
27
  Promise.resolve(ok({ profile: null, compatibility: {} })));
28
28
  return new SessionProviderRouter({
29
- kind: "legacy",
29
+ kind: "single",
30
30
  provider,
31
31
  identity,
32
32
  capabilities,
@@ -61,7 +61,7 @@ export class SessionProviderRouter {
61
61
  }
62
62
  /** Provider identities exposed through the flat 1.x compatibility field. */
63
63
  providerIdentities(route) {
64
- if (this.runtime.kind === "legacy") {
64
+ if (this.runtime.kind === "single") {
65
65
  if (route.capabilities === undefined)
66
66
  return [route.identity];
67
67
  return uniqueProviderIdentities([...route.capabilities.values()].map(({ provider }) => provider));
@@ -86,7 +86,7 @@ export class SessionProviderRouter {
86
86
  compatibility: {},
87
87
  binding: null,
88
88
  selection: undefined,
89
- createClient: (target, context) => createLegacyClient(runtime, target, undefined, context),
89
+ createClient: (target, context) => createSingleProviderClient(runtime, target, undefined, context),
90
90
  };
91
91
  }
92
92
  /** Resolve target routes before any provider client is created. */
@@ -122,12 +122,12 @@ export class SessionProviderRouter {
122
122
  compatibility: normalized.value.compatibility,
123
123
  binding: null,
124
124
  selection: undefined,
125
- createClient: (openedTarget, context) => createLegacyClient(runtime, openedTarget, normalized.value.profile ?? undefined, context),
125
+ createClient: (openedTarget, context) => createSingleProviderClient(runtime, openedTarget, normalized.value.profile ?? undefined, context),
126
126
  });
127
127
  }
128
128
  /** Candidate status authoritative for the active or target-free route. */
129
129
  candidateStatuses(route) {
130
- if (this.runtime.kind === "legacy")
130
+ if (this.runtime.kind === "single")
131
131
  return [];
132
132
  return route.selection?.candidates ?? this.runtime.registry.candidates();
133
133
  }
@@ -160,7 +160,7 @@ const selectableRoute = (auxiliaryProviders, selection) => {
160
160
  emptyClient(identity),
161
161
  };
162
162
  };
163
- const createLegacyClient = (runtime, target, profile, context) => typeof runtime.provider === "function"
163
+ const createSingleProviderClient = (runtime, target, profile, context) => typeof runtime.provider === "function"
164
164
  ? runtime.provider(target, profile, context)
165
165
  : runtime.provider.createClient(target, profile, context);
166
166
  const emptyClient = (identity) => ({
@@ -5,6 +5,7 @@ import { z } from "zod";
5
5
  import writeFileAtomic from "write-file-atomic";
6
6
  import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
7
7
  import { PRODUCT_IDENTITY } from "../identity.js";
8
+ import { MCP_STARTUP_POLICY } from "../mcpStartupPolicy.js";
8
9
  import { resolveClientConfigTransactionPath } from "./ClientConfigPath.js";
9
10
  const defaultCommand = () => [
10
11
  "npx",
@@ -12,7 +13,6 @@ const defaultCommand = () => [
12
13
  PRODUCT_IDENTITY.registrationPackageSpecifier,
13
14
  "mcp",
14
15
  ];
15
- const CODEX_STARTUP_TIMEOUT_SECONDS = 30;
16
16
  /** Back up, atomically update, and semantically read back one JSON MCP configuration. */
17
17
  export const configureJsonClient = async (client, environment = {}, command = defaultCommand()) => {
18
18
  const transactionPath = await resolveClientConfigTransactionPath(client.configPath);
@@ -214,7 +214,9 @@ const clientConfigurationDesired = (client, providerEnvironment, command) => {
214
214
  command: command[0] ?? PRODUCT_IDENTITY.cliBinary,
215
215
  args: command.slice(1),
216
216
  ...(client.name === "codex"
217
- ? { startup_timeout_sec: CODEX_STARTUP_TIMEOUT_SECONDS }
217
+ ? {
218
+ startup_timeout_sec: MCP_STARTUP_POLICY.codexStartupTimeoutSeconds,
219
+ }
218
220
  : {}),
219
221
  ...(Object.keys(environment).length === 0 ? {} : { env: environment }),
220
222
  };
@@ -1,12 +1,10 @@
1
- import { BinarySession } from "./BinarySession.js";
2
1
  import { HopperProvider } from "../hopper/HopperProvider.js";
3
2
  import { GhidraProvider } from "../ghidra/GhidraProvider.js";
4
- import { NativeMacOSProvider } from "../native/NativeMacOSProvider.js";
5
- import { ArtifactProvider } from "../artifacts/ArtifactProvider.js";
6
- import { ManagedStaticProvider } from "../dotnet/ManagedStaticProvider.js";
7
3
  import { silentLogger } from "../logger.js";
4
+ import { GENERATED_AUXILIARY_PROVIDERS } from "../generatedMcpToolCatalog.js";
8
5
  import { AnalysisProviderRegistry } from "./AnalysisProviderRegistry.js";
9
- import { SessionProviderRouter } from "./SessionProviderRouter.js";
6
+ import { composeBinarySession } from "./BinarySessionComposition.js";
7
+ import { LazyAnalysisProvider } from "./LazyAnalysisProvider.js";
10
8
  /**
11
9
  * Compose the target-switching runtime shared directly by CLI and MCP adapters.
12
10
  * This is the sole production wiring point, so both adapters share identical
@@ -15,9 +13,28 @@ import { SessionProviderRouter } from "./SessionProviderRouter.js";
15
13
  export const createBinarySession = (config, logger = silentLogger) => {
16
14
  const hopper = new HopperProvider(config, logger);
17
15
  const ghidra = new GhidraProvider(config, logger);
18
- return new BinarySession(SessionProviderRouter.selectable(new AnalysisProviderRegistry([hopper, ghidra], config.analysisProvider), [
19
- new ArtifactProvider(config.artifactNativeMountEnabled, config.artifactIntegrityContinueEnabled),
20
- new NativeMacOSProvider(),
21
- new ManagedStaticProvider(),
16
+ const auxiliary = new Map(GENERATED_AUXILIARY_PROVIDERS.map((provider) => [
17
+ provider.identity.id,
18
+ provider,
22
19
  ]));
20
+ const lazyProvider = (id, load) => {
21
+ const generated = auxiliary.get(id);
22
+ if (generated === undefined)
23
+ throw new TypeError(`Missing generated provider metadata for ${id}`);
24
+ return new LazyAnalysisProvider({ ...generated, load });
25
+ };
26
+ return composeBinarySession(new AnalysisProviderRegistry([hopper, ghidra], config.analysisProvider), [
27
+ lazyProvider("rea-artifact-graph", async () => {
28
+ const { ArtifactProvider } = await import("../artifacts/ArtifactProvider.js");
29
+ return new ArtifactProvider(config.artifactNativeMountEnabled, config.artifactIntegrityContinueEnabled);
30
+ }),
31
+ lazyProvider("native-macos", async () => {
32
+ const { NativeMacOSProvider } = await import("../native/NativeMacOSProvider.js");
33
+ return new NativeMacOSProvider();
34
+ }),
35
+ lazyProvider("rea-dotnet-static", async () => {
36
+ const { ManagedStaticProvider } = await import("../dotnet/ManagedStaticProvider.js");
37
+ return new ManagedStaticProvider();
38
+ }),
39
+ ]);
23
40
  };
@@ -3,8 +3,14 @@ import { browserScenarioCaptureSchema, browserScenarioCompletenessSchema, browse
3
3
  import { AnalysisError, BrowserObservationError, ProviderAdapterError, } from "../domain/errors.js";
4
4
  import { err, ok } from "../domain/result.js";
5
5
  import { BrowserScenarioCaptureBudget } from "./PlaywrightScenarioArtifacts.js";
6
- import { PlaywrightScenarioSessionFactory } from "./PlaywrightScenarioSession.js";
7
6
  const OPERATION = "capture_browser_scenario";
7
+ let defaultFactory;
8
+ const lazyPlaywrightFactory = {
9
+ async open(scenario, options) {
10
+ defaultFactory ??= import("./PlaywrightScenarioSession.js").then(({ PlaywrightScenarioSessionFactory }) => new PlaywrightScenarioSessionFactory());
11
+ return (await defaultFactory).open(scenario, options);
12
+ },
13
+ };
8
14
  export const PLAYWRIGHT_BROWSER_SCENARIO_PROVIDER_IDENTITY = Object.freeze({
9
15
  id: "rea-playwright-browser-scenario",
10
16
  name: "REA Playwright browser scenario capture provider",
@@ -234,7 +240,7 @@ const runScenario = async (factory, scenario, options) => {
234
240
  /** Controlled Playwright/CDP scenario driver with exact process ownership. */
235
241
  export class PlaywrightBrowserScenarioProvider {
236
242
  factory;
237
- constructor(factory = new PlaywrightScenarioSessionFactory()) {
243
+ constructor(factory = lazyPlaywrightFactory) {
238
244
  this.factory = factory;
239
245
  }
240
246
  identity() {
@@ -29,7 +29,7 @@ const promptCatalog = PROMPT_CONTRACTS.map((contract) => ({
29
29
  /** Canonical fixed resources exposed by every target-free MCP session. */
30
30
  export const MCP_RESOURCE_CATALOG = [
31
31
  { name: "server-identity", uri: "rea://server/identity" },
32
- { name: "active-residual-unknowns", uri: "rea://unknowns/active" },
32
+ { name: "analysis-snapshot", uri: "rea://snapshot/current" },
33
33
  ];
34
34
  /** Canonical resource templates exposed by every target-free MCP session. */
35
35
  export const MCP_RESOURCE_TEMPLATE_CATALOG = [
@@ -40,8 +40,8 @@ export const MCP_RESOURCE_TEMPLATE_CATALOG = [
40
40
  },
41
41
  { name: "residual-unknown", uri_template: "rea://unknown/{unknownId}" },
42
42
  {
43
- name: "analysis-snapshot",
44
- uri_template: "rea://snapshot/{snapshotDigest}",
43
+ name: "evidence-bundle",
44
+ uri_template: "rea://evidence-bundle/{bundleDigest}",
45
45
  },
46
46
  {
47
47
  name: "artifact-page",
@@ -73,17 +73,19 @@ const registerExtractionCommand = (cli, logger) => {
73
73
  args: z.object({
74
74
  path: z.string().describe("Application or package path"),
75
75
  outputRoot: z.string().describe("Absent absolute output root"),
76
+ }),
77
+ options: z.object({
76
78
  occurrenceIds: z
77
79
  .array(z.string().regex(/^occ_[a-f0-9]{64}$/u))
78
80
  .min(1)
79
81
  .max(500)
80
82
  .describe("Exact artifact occurrence IDs selected for extraction"),
81
83
  }),
82
- alias: { outputRoot: "output-root", occurrenceIds: "occurrence-ids" },
83
- run: ({ args }) => logCliCommand(logger, "extract-artifact", () => runProviderAnalysis(args.path, "extract_artifact", {
84
+ alias: { occurrenceIds: "occurrence-ids" },
85
+ run: ({ args, options }) => logCliCommand(logger, "extract-artifact", () => runProviderAnalysis(args.path, "extract_artifact", {
84
86
  approved: true,
85
87
  output_root: args.outputRoot,
86
- occurrence_ids: args.occurrenceIds,
88
+ occurrence_ids: options.occurrenceIds,
87
89
  }, logger)),
88
90
  });
89
91
  };
@@ -9,6 +9,7 @@ import { runCliJavaScriptApplicationAnalysis } from "./javascriptApplicationAnal
9
9
  export const registerCoreAnalysisCommands = (cli, logger) => {
10
10
  registerCoreCommands(cli, logger);
11
11
  registerFunctionCommand(cli, logger);
12
+ registerNativeApiCommand(cli, logger);
12
13
  registerInstructionsCommand(cli, logger);
13
14
  registerSearchCommand(cli, logger);
14
15
  registerXrefsCommand(cli, logger);
@@ -213,6 +214,46 @@ const registerFunctionCommand = (cli, logger) => {
213
214
  }, directAnalysisOptions(logger, options.snapshot, options.provider))),
214
215
  });
215
216
  };
217
+ const registerNativeApiCommand = (cli, logger) => {
218
+ cli.command(CLI_COMMANDS.inspectNativeApi, {
219
+ description: "Reconstruct one native function API boundary with evidence",
220
+ args: z.object({
221
+ path: z.string().describe("App or program path"),
222
+ address: z.string().describe("Procedure name or address"),
223
+ }),
224
+ options: z.object({
225
+ maxPseudocodeChars: z
226
+ .number()
227
+ .int()
228
+ .min(1)
229
+ .max(100_000)
230
+ .default(20_000)
231
+ .describe("Maximum pseudocode characters to inspect"),
232
+ maxInstructions: z
233
+ .number()
234
+ .int()
235
+ .min(1)
236
+ .max(5_000)
237
+ .default(500)
238
+ .describe("Maximum assembly instructions to inspect"),
239
+ snapshot: z
240
+ .string()
241
+ .min(1)
242
+ .optional()
243
+ .describe("Load and update a local analysis snapshot"),
244
+ provider: providerSelectionOption,
245
+ }),
246
+ alias: {
247
+ maxPseudocodeChars: "max-pseudocode-chars",
248
+ maxInstructions: "max-instructions",
249
+ },
250
+ run: ({ args, options }) => logCliCommand(logger, "inspect-native-api", () => runDirectAnalysis(args.path, "inspect_native_api", {
251
+ procedure: args.address,
252
+ max_pseudocode_chars: options.maxPseudocodeChars,
253
+ max_instructions: options.maxInstructions,
254
+ }, directAnalysisOptions(logger, options.snapshot, options.provider))),
255
+ });
256
+ };
216
257
  const registerInstructionsCommand = (cli, logger) => {
217
258
  cli.command(CLI_COMMANDS.instructions, {
218
259
  description: "Read one bounded raw-instruction window without decompiling",
@@ -12,6 +12,7 @@ export const CLI_COMMANDS = Object.freeze({
12
12
  capabilities: "capabilities",
13
13
  providers: "providers",
14
14
  function: "function",
15
+ inspectNativeApi: "inspect-native-api",
15
16
  instructions: "instructions",
16
17
  search: "search",
17
18
  importReferenceSource: "import-reference-source",
@@ -1,3 +1,4 @@
1
+ import { isAbsolute } from "node:path";
1
2
  import { z } from "zod";
2
3
  import { artifactOutputSchemas } from "./toolOutputSchemas.js";
3
4
  import { jsonValueSchema } from "../domain/jsonValue.js";
@@ -51,7 +52,11 @@ export const artifactInventoryInputSchema = z
51
52
  /** Exact caller boundary for approved artifact extraction. */
52
53
  export const artifactExtractionInputSchema = z.object({
53
54
  approved: z.literal(true),
54
- output_root: z.string().min(1).max(4_096),
55
+ output_root: z
56
+ .string()
57
+ .min(1)
58
+ .max(4_096)
59
+ .refine(isAbsolute, "Extraction output root must be absolute"),
55
60
  occurrence_ids: z
56
61
  .array(z.string().regex(/^occ_[a-f0-9]{64}$/u))
57
62
  .min(1)
@@ -110,7 +115,7 @@ const artifact = (name, description, inputSchema) => {
110
115
  };
111
116
  /** Artifact-graph inventory and explicitly approved safe extraction contracts. */
112
117
  export const ARTIFACT_TOOL_CONTRACTS = [
113
- artifact("inventory_artifact", "Inventory the active application or package as a deterministic, content-addressed artifact graph. Returns bounded node and edge pages without extracting or mounting by default.", artifactInventoryInputSchema),
114
- artifact("inspect_artifact", "Inspect the active artifact through one bounded, cancellable inventory substep. Returns the full substep Evidence and its ID, observations, derived relationships, hypotheses, contradictions, unexplored branches, limitations, and format-specific next probes. Any substep failure fails the whole call.", artifactInspectionInputSchema),
118
+ artifact("inventory_artifact", "After open_binary binds a local archive, application package, or other artifact—or when one is already active—inventory it as a deterministic, content-addressed artifact graph. This tool accepts no path; in a target-free session open the target first. Returns bounded node and edge pages without extracting or mounting by default.", artifactInventoryInputSchema),
119
+ artifact("inspect_artifact", "After open_binary binds a local archive, application package, or other artifact—or when one is already active—inspect it through one bounded, cancellable inventory substep. This tool accepts no path; in a target-free session open the target first. Returns the full substep Evidence and its ID, observations, derived relationships, hypotheses, contradictions, unexplored branches, limitations, and format-specific next probes. Any substep failure fails the whole call.", artifactInspectionInputSchema),
115
120
  artifact("extract_artifact", "Extract selected graph artifacts beneath an explicit output root. Requires approval, rejects traversal and symlink escapes, never overwrites, enforces bomb limits, and verifies cleanup.", artifactExtractionInputSchema),
116
121
  ];
@@ -61,6 +61,15 @@ export const enhancedInputSchemas = {
61
61
  basic_blocks: 0,
62
62
  }),
63
63
  }),
64
+ inspect_native_api: z.object({
65
+ procedure: z.string().describe("A procedure name or address"),
66
+ max_pseudocode_chars: z.number().int().min(1).max(100_000).default(20_000),
67
+ max_instructions: z.number().int().min(1).max(5_000).default(500),
68
+ unknown_registry_approved: z
69
+ .literal(true)
70
+ .optional()
71
+ .describe("Explicit approval to record unsupported native API branches durably"),
72
+ }),
64
73
  trace_feature: traceLiteralInputSchema,
65
74
  find_code_for_string: traceLiteralInputSchema,
66
75
  trace_call_path: z.object({
@@ -90,6 +99,7 @@ export const enhancedToolNameSchema = z.enum([
90
99
  "find_xrefs_to_name",
91
100
  "binary_overview",
92
101
  "analyze_function",
102
+ "inspect_native_api",
93
103
  "trace_feature",
94
104
  "find_code_for_string",
95
105
  "trace_call_path",
@@ -0,0 +1,30 @@
1
+ import { jsonObjectSchema } from "../domain/jsonValue.js";
2
+ import { enhancedInputSchemas } from "./enhancedInputs.js";
3
+ import { TOOL_EXAMPLE_OVERRIDES } from "./toolContractExamples.js";
4
+ import { toolContractMetadata } from "./toolEffects.js";
5
+ import { enhancedOutputSchemas } from "./toolOutputSchemas.js";
6
+ const functionWorkflow = (name, description) => {
7
+ const inputSchema = enhancedInputSchemas[name];
8
+ const outputSchema = enhancedOutputSchemas[name];
9
+ if (outputSchema === undefined)
10
+ throw new Error(`Missing function workflow output schema for ${name}`);
11
+ return {
12
+ name,
13
+ ...toolContractMetadata(name),
14
+ description,
15
+ kind: "enhanced",
16
+ inputSchema,
17
+ outputSchema,
18
+ examples: [
19
+ {
20
+ title: `Example ${name.replaceAll("_", " ")} request`,
21
+ input: jsonObjectSchema.parse(inputSchema.parse(TOOL_EXAMPLE_OVERRIDES[name] ?? {})),
22
+ },
23
+ ],
24
+ };
25
+ };
26
+ /** Function dossier and native API reconstruction workflow contracts. */
27
+ export const FUNCTION_WORKFLOW_TOOL_CONTRACTS = [
28
+ functionWorkflow("analyze_function", "Preferred bounded analysis for one procedure symbol or address. Returns identity, provider-specific pseudocode, optional assembly, comments, calls, typed-or-explicitly-unavailable references, referenced strings/names, and local CFG blocks with exact truncation metadata. Providers with a structured decompiler model also expose native API boundary types, confidence, evidence, jump-table data/target addresses, and explicit decompiler-artifact labels."),
29
+ functionWorkflow("inspect_native_api", "Reconstruct one native function boundary through inspectable substeps. Returns structured inferred return/parameter types with confidence and evidence, jump-table dispatch/data/target mappings, explicit decompiler-artifact labels, unsupported branches, and residual unknowns."),
30
+ ];