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
|
@@ -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: "
|
|
5
|
-
serverSdkVersion: "2.0.0
|
|
6
|
-
clientSdkVersion: "2.0.0
|
|
7
|
-
coreSdkVersion: "2.0.0
|
|
4
|
+
version: "3.0.0", // x-release-please-version
|
|
5
|
+
serverSdkVersion: "2.0.0",
|
|
6
|
+
clientSdkVersion: "2.0.0",
|
|
7
|
+
coreSdkVersion: "2.0.0",
|
|
8
8
|
skillVersion: "23",
|
|
9
9
|
};
|
|
@@ -17,4 +17,4 @@ export const GHIDRA_MAX_QUEUED_REQUESTS = 32;
|
|
|
17
17
|
/** Maximum encoded response line accepted from the Java bridge. */
|
|
18
18
|
export const GHIDRA_MAX_LINE_BYTES = 1024 * 1024;
|
|
19
19
|
/** Version of REA's Java bridge handshake and request protocol. */
|
|
20
|
-
export const GHIDRA_BRIDGE_VERSION =
|
|
20
|
+
export const GHIDRA_BRIDGE_VERSION = 6;
|
|
@@ -2,6 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import { AnalysisInputError, AnalysisOutputError } from "../domain/errors.js";
|
|
3
3
|
import { functionInstructionWindowSchema, functionDossierSchema, } from "../domain/hopperValues.js";
|
|
4
4
|
import { jsonObjectSchema, jsonValueSchema, } from "../domain/jsonValue.js";
|
|
5
|
+
import { nativeApiBoundarySchema } from "../domain/nativeApiBoundary.js";
|
|
5
6
|
import { err, ok } from "../domain/result.js";
|
|
6
7
|
import { ghidraBoundedIdentifierSchema, ghidraCanonicalAddressSchema, } from "./GhidraInventoryValues.js";
|
|
7
8
|
/** Read-only function operations admitted by the Ghidra adapter. */
|
|
@@ -191,7 +192,32 @@ const procedureInfo = z
|
|
|
191
192
|
classification,
|
|
192
193
|
})
|
|
193
194
|
.strict();
|
|
194
|
-
const
|
|
195
|
+
const ghidraNativeApiBoundary = nativeApiBoundarySchema.superRefine((value, context) => {
|
|
196
|
+
if (!value.available)
|
|
197
|
+
return;
|
|
198
|
+
if (value.provenance !== "ghidra-high-function")
|
|
199
|
+
context.addIssue({
|
|
200
|
+
code: "custom",
|
|
201
|
+
message: "Ghidra native API observations require HighFunction provenance",
|
|
202
|
+
});
|
|
203
|
+
const addresses = value.jump_tables.flatMap((table) => [
|
|
204
|
+
table.dispatch_address,
|
|
205
|
+
...table.data_sources.map(({ address }) => address),
|
|
206
|
+
...table.mappings.flatMap(({ target_address, data_addresses }) => [
|
|
207
|
+
target_address,
|
|
208
|
+
...data_addresses,
|
|
209
|
+
]),
|
|
210
|
+
]);
|
|
211
|
+
if (addresses.some((address) => !ghidraCanonicalAddressSchema.safeParse(address).success))
|
|
212
|
+
context.addIssue({
|
|
213
|
+
code: "custom",
|
|
214
|
+
message: "Ghidra native API observation contains a non-canonical address",
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
const ghidraFunctionDossier = functionDossierSchema
|
|
218
|
+
.extend({ native_api: ghidraNativeApiBoundary })
|
|
219
|
+
.strict()
|
|
220
|
+
.superRefine((value, context) => {
|
|
195
221
|
const addresses = [
|
|
196
222
|
value.procedure.address,
|
|
197
223
|
...value.comments.items.map(({ address }) => address),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { GENERATED_MCP_TOOL_CATALOG } from "../generatedMcpToolCatalog.js";
|
|
2
2
|
import { GHIDRA_DECOMPILE_REQUEST_TIMEOUT_MS, GHIDRA_MAX_LINE_BYTES, GHIDRA_REQUEST_TIMEOUT_MS, } from "./GhidraDefaults.js";
|
|
3
3
|
import { GHIDRA_FUNCTION_OPERATIONS } from "./GhidraFunctionValues.js";
|
|
4
4
|
import { GHIDRA_INVENTORY_OPERATIONS } from "./GhidraInventoryValues.js";
|
|
@@ -8,10 +8,7 @@ export const GHIDRA_PROVIDER_IDENTITY = Object.freeze({
|
|
|
8
8
|
name: "Ghidra",
|
|
9
9
|
version: null,
|
|
10
10
|
});
|
|
11
|
-
const providerContractByName = new Map(
|
|
12
|
-
contract.name,
|
|
13
|
-
contract,
|
|
14
|
-
]));
|
|
11
|
+
const providerContractByName = new Map(GENERATED_MCP_TOOL_CATALOG.filter(({ kind }) => kind === "official-proxy" || kind === "enhanced").map((contract) => [contract.name, contract]));
|
|
15
12
|
/** Provider-neutral read-only contracts implemented by the Ghidra adapter. */
|
|
16
13
|
export const GHIDRA_PROVIDER_TOOL_CONTRACTS = Object.freeze([...GHIDRA_INVENTORY_OPERATIONS, ...GHIDRA_FUNCTION_OPERATIONS].map((operation) => {
|
|
17
14
|
const contract = providerContractByName.get(operation);
|
|
@@ -137,39 +134,44 @@ export const limitationsFor = (operation) => {
|
|
|
137
134
|
}
|
|
138
135
|
};
|
|
139
136
|
/** Provider-neutral capabilities advertised by every non-Windows Ghidra session. */
|
|
140
|
-
export const CAPABILITIES = Object.freeze(GHIDRA_PROVIDER_TOOL_CONTRACTS.map((contract) =>
|
|
141
|
-
|
|
142
|
-
operation
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
:
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
:
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
137
|
+
export const CAPABILITIES = Object.freeze(GHIDRA_PROVIDER_TOOL_CONTRACTS.map((contract) => {
|
|
138
|
+
const operation = contract.analysisOperation;
|
|
139
|
+
if (operation === null)
|
|
140
|
+
throw new TypeError(`Missing analysis operation for ${contract.name}`);
|
|
141
|
+
return Object.freeze({
|
|
142
|
+
provider: GHIDRA_PROVIDER_IDENTITY,
|
|
143
|
+
operation,
|
|
144
|
+
inputContractVersion: 1,
|
|
145
|
+
outputContractVersion: 1,
|
|
146
|
+
available: true,
|
|
147
|
+
reason: null,
|
|
148
|
+
pagination: PAGINATED_OPERATIONS.has(contract.name)
|
|
149
|
+
? "offset"
|
|
150
|
+
: "none",
|
|
151
|
+
exhaustive: !PAGINATED_OPERATIONS.has(contract.name),
|
|
152
|
+
effects: Object.freeze({
|
|
153
|
+
mutatesArtifact: false,
|
|
154
|
+
launchesProcess: true,
|
|
155
|
+
mayShowUi: false,
|
|
156
|
+
mayAccessNetwork: false,
|
|
157
|
+
mayWriteFilesystem: true,
|
|
158
|
+
changesPermissions: false,
|
|
159
|
+
requiresRoot: false,
|
|
160
|
+
}),
|
|
161
|
+
limits: Object.freeze({
|
|
162
|
+
maxResults: PAGINATED_OPERATIONS.has(contract.name)
|
|
163
|
+
? SEARCH_OPERATIONS.has(contract.name)
|
|
164
|
+
? 100
|
|
165
|
+
: 500
|
|
166
|
+
: null,
|
|
167
|
+
maxPayloadBytes: GHIDRA_MAX_LINE_BYTES,
|
|
168
|
+
timeoutMs: DECOMPILE_OPERATIONS.has(contract.name)
|
|
169
|
+
? GHIDRA_DECOMPILE_REQUEST_TIMEOUT_MS
|
|
170
|
+
: GHIDRA_REQUEST_TIMEOUT_MS,
|
|
171
|
+
}),
|
|
172
|
+
limitations: Object.freeze(limitationsFor(contract.name)),
|
|
173
|
+
});
|
|
174
|
+
}));
|
|
173
175
|
/** Capabilities advertised for the experimental Windows x64 P0 boundary. */
|
|
174
176
|
export const WINDOWS_P0_CAPABILITIES = Object.freeze(CAPABILITIES.map((capability) => Object.freeze({
|
|
175
177
|
...capability,
|
|
@@ -15,7 +15,7 @@ import { connectHopperSocketOnce } from "./HopperSocketConnection.js";
|
|
|
15
15
|
import { hopperLauncherFailureDiagnostic } from "./HopperProcessDiagnostic.js";
|
|
16
16
|
import { HopperRequestQueue, } from "./HopperRequestQueue.js";
|
|
17
17
|
import { HopperResponseStream } from "./HopperResponseStream.js";
|
|
18
|
-
import { responseResult } from "./protocol.js";
|
|
18
|
+
import { responseResult, } from "./protocol.js";
|
|
19
19
|
const SHUTDOWN_TIMEOUT_MS = 30_000;
|
|
20
20
|
const MAX_QUEUED_REQUESTS = 64;
|
|
21
21
|
const SESSION_ROOT = process.platform === "darwin" ? "/tmp" : tmpdir();
|
|
@@ -76,7 +76,7 @@ export class HopperClient {
|
|
|
76
76
|
});
|
|
77
77
|
});
|
|
78
78
|
this.#responses = new HopperResponseStream({
|
|
79
|
-
accept: (
|
|
79
|
+
accept: (message) => this.#acceptBridgeMessage(message),
|
|
80
80
|
hasQueued: (id) => this.#requests.hasQueued(id),
|
|
81
81
|
nextRequestId: () => this.#nextId,
|
|
82
82
|
abort: (message, cause) => this.#abortProtocol(message, cause),
|
|
@@ -333,6 +333,30 @@ export class HopperClient {
|
|
|
333
333
|
socket.on("error", this.#onSocketError);
|
|
334
334
|
socket.on("close", this.#onSocketClose);
|
|
335
335
|
}
|
|
336
|
+
#acceptBridgeMessage(message) {
|
|
337
|
+
if (!("event" in message))
|
|
338
|
+
return this.#requests.accept(message.id, responseResult(message));
|
|
339
|
+
if (!this.#requests.acceptEvent(message))
|
|
340
|
+
return false;
|
|
341
|
+
this.#emitBridgeDiagnostic(message);
|
|
342
|
+
return true;
|
|
343
|
+
}
|
|
344
|
+
#emitBridgeDiagnostic(message) {
|
|
345
|
+
if (message.event.type !== "diagnostic")
|
|
346
|
+
return;
|
|
347
|
+
try {
|
|
348
|
+
this.#options.onDiagnostic?.({
|
|
349
|
+
type: "bridge-diagnostic",
|
|
350
|
+
request_id: message.id,
|
|
351
|
+
code: message.event.error.code,
|
|
352
|
+
category: message.event.error.type,
|
|
353
|
+
message: message.event.error.message,
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
catch {
|
|
357
|
+
this.#logger.warn({ requestId: message.id }, "Hopper bridge diagnostic consumer rejected an event");
|
|
358
|
+
}
|
|
359
|
+
}
|
|
336
360
|
#detachSocket(socket) {
|
|
337
361
|
socket.off("data", this.#onSocketData);
|
|
338
362
|
socket.off("error", this.#onSocketError);
|
|
@@ -3,7 +3,7 @@ import { accessSync, constants } from "node:fs";
|
|
|
3
3
|
import { createAnalysisExecution } from "../application/AnalysisProvider.js";
|
|
4
4
|
import { AnalysisCapabilityUnavailableError } from "../domain/errors.js";
|
|
5
5
|
import { err } from "../domain/result.js";
|
|
6
|
-
import {
|
|
6
|
+
import { GENERATED_MCP_TOOL_CATALOG } from "../generatedMcpToolCatalog.js";
|
|
7
7
|
import { HopperApplicationLauncher } from "./BridgeLauncher.js";
|
|
8
8
|
import { hopperLoaderArgsForTarget, resolveHopperAnalysisProfile, } from "./HopperAnalysisProfile.js";
|
|
9
9
|
import { HopperClient } from "./HopperClient.js";
|
|
@@ -24,36 +24,47 @@ const MUTATING_OPERATIONS = new Set([
|
|
|
24
24
|
]);
|
|
25
25
|
/** Tool contracts implemented directly by the Hopper adapter. */
|
|
26
26
|
export const HOPPER_PROVIDER_TOOL_CONTRACTS = Object.freeze([
|
|
27
|
-
...
|
|
28
|
-
...
|
|
27
|
+
...GENERATED_MCP_TOOL_CATALOG.filter(({ kind }) => kind === "official-proxy"),
|
|
28
|
+
...GENERATED_MCP_TOOL_CATALOG.filter(({ name }) => name === "analyze_function"),
|
|
29
29
|
]);
|
|
30
|
-
const CAPABILITIES = Object.freeze(HOPPER_PROVIDER_TOOL_CONTRACTS.map((contract) =>
|
|
31
|
-
|
|
32
|
-
operation
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
30
|
+
const CAPABILITIES = Object.freeze(HOPPER_PROVIDER_TOOL_CONTRACTS.map((contract) => {
|
|
31
|
+
const operation = contract.analysisOperation;
|
|
32
|
+
if (operation === null)
|
|
33
|
+
throw new TypeError(`Missing analysis operation for ${contract.name}`);
|
|
34
|
+
return Object.freeze({
|
|
35
|
+
provider: IDENTITY,
|
|
36
|
+
operation,
|
|
37
|
+
inputContractVersion: 1,
|
|
38
|
+
outputContractVersion: 1,
|
|
39
|
+
available: true,
|
|
40
|
+
reason: null,
|
|
41
|
+
pagination: isRecord(contract.inputSchema.properties) &&
|
|
42
|
+
"offset" in contract.inputSchema.properties
|
|
43
|
+
? "offset"
|
|
44
|
+
: "none",
|
|
45
|
+
exhaustive: false,
|
|
46
|
+
effects: Object.freeze({
|
|
47
|
+
mutatesArtifact: MUTATING_OPERATIONS.has(contract.name),
|
|
48
|
+
launchesProcess: true,
|
|
49
|
+
mayShowUi: true,
|
|
50
|
+
mayAccessNetwork: false,
|
|
51
|
+
mayWriteFilesystem: MUTATING_OPERATIONS.has(contract.name),
|
|
52
|
+
changesPermissions: false,
|
|
53
|
+
requiresRoot: false,
|
|
54
|
+
}),
|
|
55
|
+
limits: Object.freeze({
|
|
56
|
+
maxResults: null,
|
|
57
|
+
maxPayloadBytes: contract.name === "read_bytes" ? 4_096 : null,
|
|
58
|
+
timeoutMs: null,
|
|
59
|
+
}),
|
|
60
|
+
limitations: Object.freeze([
|
|
61
|
+
"Results depend on Hopper's completed static analysis.",
|
|
62
|
+
]),
|
|
63
|
+
});
|
|
64
|
+
}));
|
|
65
|
+
function isRecord(value) {
|
|
66
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
67
|
+
}
|
|
57
68
|
/** Concrete analysis provider backed by REA's private Hopper bridge. */
|
|
58
69
|
export class HopperProvider {
|
|
59
70
|
config;
|
|
@@ -178,6 +189,7 @@ export class HopperProvider {
|
|
|
178
189
|
}),
|
|
179
190
|
...(context === undefined ? {} : { runId: context.runId }),
|
|
180
191
|
logger: this.logger.child({ layer: "bridge" }),
|
|
192
|
+
onDiagnostic: (diagnostic) => this.logger.info(diagnostic, "Hopper bridge reported a diagnostic"),
|
|
181
193
|
});
|
|
182
194
|
return {
|
|
183
195
|
execute: async (operation, parameters, options) => {
|
|
@@ -59,6 +59,28 @@ export class HopperRequestQueue {
|
|
|
59
59
|
this.#drain();
|
|
60
60
|
return true;
|
|
61
61
|
}
|
|
62
|
+
/** Forward one correlated bridge event without releasing the wire request. */
|
|
63
|
+
acceptEvent(event) {
|
|
64
|
+
const entry = this.#active;
|
|
65
|
+
if (entry === undefined || entry.id !== event.id)
|
|
66
|
+
return false;
|
|
67
|
+
if (event.event.type === "progress" && !entry.callerSettled) {
|
|
68
|
+
if (event.event.terminal === true && entry.heartbeat !== undefined) {
|
|
69
|
+
clearInterval(entry.heartbeat);
|
|
70
|
+
entry.heartbeat = undefined;
|
|
71
|
+
}
|
|
72
|
+
this.#reportUpdate(entry, {
|
|
73
|
+
phase: event.event.phase,
|
|
74
|
+
completed: event.event.completed,
|
|
75
|
+
total: event.event.total,
|
|
76
|
+
message: event.event.message,
|
|
77
|
+
...(event.event.terminal === undefined
|
|
78
|
+
? {}
|
|
79
|
+
: { terminal: event.event.terminal }),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
62
84
|
/** Fail the active wire request and every request still waiting in the FIFO. */
|
|
63
85
|
failAll(error) {
|
|
64
86
|
const active = this.#active;
|
|
@@ -175,13 +197,19 @@ export class HopperRequestQueue {
|
|
|
175
197
|
entry.signal.removeEventListener("abort", entry.onAbort);
|
|
176
198
|
}
|
|
177
199
|
#report(entry, message) {
|
|
178
|
-
|
|
179
|
-
?.report({
|
|
200
|
+
this.#reportUpdate(entry, {
|
|
180
201
|
phase: "hopper_request",
|
|
181
202
|
completed: 0,
|
|
182
203
|
total: 1,
|
|
183
204
|
message,
|
|
184
|
-
})
|
|
185
|
-
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
#reportUpdate(entry, update) {
|
|
208
|
+
try {
|
|
209
|
+
void Promise.resolve(entry.progress?.report(update)).catch(() => undefined);
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
// Progress observation cannot change the request outcome.
|
|
213
|
+
}
|
|
186
214
|
}
|
|
187
215
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { parseBridgeMessageLine, } from "./protocol.js";
|
|
2
2
|
const MAX_LINE_BYTES = 10 * 1024 * 1024;
|
|
3
3
|
/** Incrementally validates bounded Hopper NDJSON response frames. */
|
|
4
4
|
export class HopperResponseStream {
|
|
@@ -29,7 +29,7 @@ export class HopperResponseStream {
|
|
|
29
29
|
this.#buffer = "";
|
|
30
30
|
}
|
|
31
31
|
#acceptLine(line) {
|
|
32
|
-
const parsed =
|
|
32
|
+
const parsed = parseBridgeMessageLine(line);
|
|
33
33
|
if (!parsed.ok) {
|
|
34
34
|
this.options.abort(parsed.error.message, parsed.error);
|
|
35
35
|
return false;
|
package/dist/hopper/protocol.js
CHANGED
|
@@ -2,30 +2,72 @@ import { z } from "zod";
|
|
|
2
2
|
import { HopperProtocolError, HopperRemoteError } from "../domain/errors.js";
|
|
3
3
|
import { jsonValueSchema } from "../domain/jsonValue.js";
|
|
4
4
|
import { err, ok } from "../domain/result.js";
|
|
5
|
+
const remoteErrorSchema = z
|
|
6
|
+
.object({
|
|
7
|
+
code: z.number().int(),
|
|
8
|
+
message: z.string().max(512),
|
|
9
|
+
type: z
|
|
10
|
+
.enum([
|
|
11
|
+
"remote",
|
|
12
|
+
"authorization",
|
|
13
|
+
"invalid_request",
|
|
14
|
+
"capability_unavailable",
|
|
15
|
+
"bridge_exception",
|
|
16
|
+
])
|
|
17
|
+
.default("remote"),
|
|
18
|
+
})
|
|
19
|
+
.strict();
|
|
5
20
|
const responseSchema = z.union([
|
|
6
|
-
z
|
|
21
|
+
z
|
|
22
|
+
.object({
|
|
7
23
|
id: z.number().int().nonnegative(),
|
|
8
24
|
result: jsonValueSchema,
|
|
9
|
-
})
|
|
10
|
-
|
|
25
|
+
})
|
|
26
|
+
.strict(),
|
|
27
|
+
z
|
|
28
|
+
.object({
|
|
11
29
|
id: z.number().int().nonnegative(),
|
|
12
|
-
error:
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
type: z
|
|
16
|
-
.enum([
|
|
17
|
-
"remote",
|
|
18
|
-
"authorization",
|
|
19
|
-
"invalid_request",
|
|
20
|
-
"capability_unavailable",
|
|
21
|
-
"bridge_exception",
|
|
22
|
-
])
|
|
23
|
-
.default("remote"),
|
|
24
|
-
}),
|
|
25
|
-
}),
|
|
30
|
+
error: remoteErrorSchema,
|
|
31
|
+
})
|
|
32
|
+
.strict(),
|
|
26
33
|
]);
|
|
27
|
-
|
|
28
|
-
|
|
34
|
+
const eventSchema = z
|
|
35
|
+
.object({
|
|
36
|
+
id: z.number().int().nonnegative(),
|
|
37
|
+
event: z.discriminatedUnion("type", [
|
|
38
|
+
z
|
|
39
|
+
.object({
|
|
40
|
+
type: z.literal("progress"),
|
|
41
|
+
phase: z.string().min(1).max(64),
|
|
42
|
+
completed: z.number().nonnegative().finite(),
|
|
43
|
+
total: z.number().nonnegative().finite().nullable(),
|
|
44
|
+
message: z.string().min(1).max(512),
|
|
45
|
+
terminal: z.boolean().optional(),
|
|
46
|
+
})
|
|
47
|
+
.strict(),
|
|
48
|
+
z
|
|
49
|
+
.object({
|
|
50
|
+
type: z.literal("diagnostic"),
|
|
51
|
+
error: remoteErrorSchema,
|
|
52
|
+
})
|
|
53
|
+
.strict(),
|
|
54
|
+
]),
|
|
55
|
+
})
|
|
56
|
+
.strict()
|
|
57
|
+
.superRefine((message, context) => {
|
|
58
|
+
if (message.event.type === "progress" &&
|
|
59
|
+
message.event.total !== null &&
|
|
60
|
+
message.event.completed > message.event.total) {
|
|
61
|
+
context.addIssue({
|
|
62
|
+
code: "custom",
|
|
63
|
+
message: "Progress total cannot be less than completed",
|
|
64
|
+
path: ["event", "total"],
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
const messageSchema = z.union([responseSchema, eventSchema]);
|
|
69
|
+
/** Parse one complete Hopper NDJSON response or event line. */
|
|
70
|
+
export const parseBridgeMessageLine = (line) => {
|
|
29
71
|
let decoded;
|
|
30
72
|
try {
|
|
31
73
|
decoded = JSON.parse(line);
|
|
@@ -33,10 +75,10 @@ export const parseResponseLine = (line) => {
|
|
|
33
75
|
catch (cause) {
|
|
34
76
|
return err(new HopperProtocolError("Hopper returned malformed JSON", { cause }));
|
|
35
77
|
}
|
|
36
|
-
const parsed =
|
|
78
|
+
const parsed = messageSchema.safeParse(decoded);
|
|
37
79
|
return parsed.success
|
|
38
80
|
? ok(parsed.data)
|
|
39
|
-
: err(new HopperProtocolError("Hopper returned a
|
|
81
|
+
: err(new HopperProtocolError("Hopper returned a message outside the bridge contract", { cause: parsed.error }));
|
|
40
82
|
};
|
|
41
83
|
/** Project a parsed response into its result or expected remote failure. */
|
|
42
84
|
export const responseResult = (response) => "error" in response
|
package/dist/main/transport.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createServer } from "../server/createServer.js";
|
|
1
|
+
import { createServer, } from "../server/createServer.js";
|
|
2
2
|
import { MCP_CONNECTION_LOST, MCP_CONNECTION_START_FAILED, } from "./messages.js";
|
|
3
3
|
export const startMcpTransport = async (dependencies, session, serverContext) => {
|
|
4
4
|
const liveServers = new Set();
|
|
@@ -13,10 +13,7 @@ export const startMcpTransport = async (dependencies, session, serverContext) =>
|
|
|
13
13
|
investigationInputRoots: serverContext.runtimeState.investigationRoots,
|
|
14
14
|
analysisSnapshotFilePolicy: serverContext.runtimeState.snapshotPolicy,
|
|
15
15
|
permissionAuthority: serverContext.permissionAuthority,
|
|
16
|
-
|
|
17
|
-
browserScenarioCapture: serverContext.browserScenarioCapture,
|
|
18
|
-
electronObservation: serverContext.electronObservation,
|
|
19
|
-
javascriptRuntimeObservation: serverContext.javascriptRuntimeObservation,
|
|
16
|
+
loadOptionalProviders: serverContext.loadOptionalProviders,
|
|
20
17
|
artifactIntegrityContinueEnabled: () => serverContext.runtimeState.currentConfig
|
|
21
18
|
.artifactIntegrityContinueEnabled,
|
|
22
19
|
javascriptReplayPolicy: serverContext.runtimeState.javascriptReplayPolicy,
|
package/dist/main.js
CHANGED
|
@@ -7,10 +7,6 @@ import { createBinarySession } from "./application/runtime.js";
|
|
|
7
7
|
import { createLogger } from "./logger.js";
|
|
8
8
|
import { projectAnalysisError } from "./domain/errors.js";
|
|
9
9
|
import { loadConfiguredPermissionAuthority } from "./application/PermissionConfiguration.js";
|
|
10
|
-
import { CdpBrowserProvider } from "./browser/CdpBrowserProvider.js";
|
|
11
|
-
import { CdpElectronProvider } from "./browser/CdpElectronProvider.js";
|
|
12
|
-
import { V8InspectorProvider } from "./browser/V8InspectorProvider.js";
|
|
13
|
-
import { PlaywrightBrowserScenarioProvider } from "./browser/PlaywrightBrowserScenarioProvider.js";
|
|
14
10
|
import { SERVER_START_FAILED } from "./main/messages.js";
|
|
15
11
|
import { createRuntimeState } from "./main/state.js";
|
|
16
12
|
import { openInitialTarget } from "./main/startup.js";
|
|
@@ -60,10 +56,6 @@ export const run = async (dependencies = runtimeDependencies()) => {
|
|
|
60
56
|
return 1;
|
|
61
57
|
}
|
|
62
58
|
const session = createBinarySession(config.value, logger);
|
|
63
|
-
const browserObservation = new CdpBrowserProvider();
|
|
64
|
-
const browserScenarioCapture = new PlaywrightBrowserScenarioProvider();
|
|
65
|
-
const electronObservation = new CdpElectronProvider();
|
|
66
|
-
const javascriptRuntimeObservation = new V8InspectorProvider();
|
|
67
59
|
const opened = await openInitialTarget(session, config.value, serverLogger, dependencies.writeStderr);
|
|
68
60
|
if (!opened.ok)
|
|
69
61
|
return opened.exitCode;
|
|
@@ -71,10 +63,20 @@ export const run = async (dependencies = runtimeDependencies()) => {
|
|
|
71
63
|
const transport = await startMcpTransport(dependencies, session, {
|
|
72
64
|
logger,
|
|
73
65
|
serverLogger,
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
66
|
+
loadOptionalProviders: async () => {
|
|
67
|
+
const [{ CdpBrowserProvider }, { PlaywrightBrowserScenarioProvider }, { CdpElectronProvider }, { V8InspectorProvider },] = await Promise.all([
|
|
68
|
+
import("./browser/CdpBrowserProvider.js"),
|
|
69
|
+
import("./browser/PlaywrightBrowserScenarioProvider.js"),
|
|
70
|
+
import("./browser/CdpElectronProvider.js"),
|
|
71
|
+
import("./browser/V8InspectorProvider.js"),
|
|
72
|
+
]);
|
|
73
|
+
return {
|
|
74
|
+
browserObservation: new CdpBrowserProvider(),
|
|
75
|
+
browserScenarioCapture: new PlaywrightBrowserScenarioProvider(),
|
|
76
|
+
electronObservation: new CdpElectronProvider(),
|
|
77
|
+
javascriptRuntimeObservation: new V8InspectorProvider(),
|
|
78
|
+
};
|
|
79
|
+
},
|
|
78
80
|
permissionAuthority: permissionAuthority.value,
|
|
79
81
|
runtimeState,
|
|
80
82
|
});
|
package/dist/mcpDoctor.js
CHANGED
|
@@ -5,12 +5,12 @@ import { resolve } from "node:path";
|
|
|
5
5
|
import { CATALOG_IDENTITY, MCP_RESOURCE_CATALOG, MCP_RESOURCE_TEMPLATE_CATALOG, } from "./catalogIdentity.js";
|
|
6
6
|
import { PROMPT_CONTRACTS } from "./contracts/promptContracts.js";
|
|
7
7
|
import { PRODUCT_IDENTITY } from "./identity.js";
|
|
8
|
-
|
|
8
|
+
import { MCP_STARTUP_POLICY } from "./mcpStartupPolicy.js";
|
|
9
9
|
const STDERR_LIMIT_BYTES = 64 * 1_024;
|
|
10
10
|
const OUTPUT_FORMATS = ["toon", "json", "yaml", "md", "jsonl"];
|
|
11
11
|
/** Diagnose the actual production stdio adapter with one bounded SDK session. */
|
|
12
12
|
export const runProductionMcpDoctor = async (options) => {
|
|
13
|
-
const deadline = Date.now() + (options.deadlineMs ??
|
|
13
|
+
const deadline = Date.now() + (options.deadlineMs ?? MCP_STARTUP_POLICY.doctorDeadlineMs);
|
|
14
14
|
const controller = new AbortController();
|
|
15
15
|
const timer = setTimeout(() => controller.abort(new Error("Production MCP doctor deadline expired")), Math.max(1, deadline - Date.now()));
|
|
16
16
|
const stderr = boundedStderr();
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supported MCP startup deadlines and regression budgets.
|
|
3
|
+
*
|
|
4
|
+
* The Codex timeout is caller-facing configuration. The smaller phase budgets
|
|
5
|
+
* preserve operational headroom, while the doctor deadline covers additional
|
|
6
|
+
* inventory requests after initialization.
|
|
7
|
+
*/
|
|
8
|
+
export const MCP_STARTUP_POLICY = {
|
|
9
|
+
codexStartupTimeoutSeconds: 30,
|
|
10
|
+
initializeBudgetMs: 15_000,
|
|
11
|
+
toolsListBudgetMs: 5_000,
|
|
12
|
+
firstCatalogBudgetMs: 20_000,
|
|
13
|
+
doctorDeadlineMs: 30_000,
|
|
14
|
+
};
|