Files
reverse-engineering-anything/src/main.ts
T
morluto e07b81649e refactor!: simplify runtime tools and remove replay engines (#572)
* fix(hopper): report unavailable procedure body coverage

* refactor!: simplify runtime tools and remove replay engines

Keep direct inspection, capture, and comparison tools. Bind handlers to named contracts and let agents compose experiments with commands and local fixture servers.

BREAKING CHANGE: Remove controlled replay, Node characterization, finite replay-machine, managed runtime planning, and redundant string-tracing tools. Retire process replay, shims, reactive scenarios, custom checkpoints, and active browser replay and origin-scope inputs.
2026-10-05 21:15:04 +08:00

136 lines
4.4 KiB
JavaScript

#!/usr/bin/env node
import { pathToFileURL } from "node:url";
import { realpathSync } from "node:fs";
import { serveStdio } from "@modelcontextprotocol/server/stdio";
import { parseConfig } from "./config.js";
import { createBinarySession } from "./application/runtime.js";
import { createLogger } from "./logger.js";
import { projectAnalysisError } from "./domain/errors.js";
import type { RuntimeDependencies } from "./main/types.js";
import { SERVER_START_FAILED } from "./main/messages.js";
import { createRuntimeState } from "./main/state.js";
import { openInitialTarget } from "./main/startup.js";
import { startMcpTransport } from "./main/transport.js";
import { registerConfigReload } from "./main/reload.js";
import { createShutdown } from "./main/shutdown.js";
const runtimeDependencies = (): RuntimeDependencies => ({
env: process.env,
serve: serveStdio,
writeStderr: (text) => process.stderr.write(text),
setExitCode: (code) => {
process.exitCode = code;
},
registerShutdown: (handler) => {
process.once("SIGINT", handler);
process.once("SIGTERM", handler);
process.stdin.once("end", handler);
process.stdin.once("close", handler);
return () => {
process.off("SIGINT", handler);
process.off("SIGTERM", handler);
process.stdin.off("end", handler);
process.stdin.off("close", handler);
};
},
registerReload: (handler) => {
process.on("SIGHUP", handler);
return () => process.off("SIGHUP", handler);
},
});
/**
* Start the long-lived MCP adapter and install idempotent shutdown handlers.
* The adapter owns its BinarySession for the process lifetime; EOF and process
* signals close bridge resources without assuming ownership of the Hopper app.
*/
export const run = async (
dependencies: RuntimeDependencies = runtimeDependencies(),
): Promise<number> => {
const config = parseConfig(dependencies.env);
if (!config.ok) {
dependencies.writeStderr(`${projectAnalysisError(config.error).message}\n`);
return 1;
}
const logger = createLogger("mcp", config.value.logLevel);
const serverLogger = logger.child({ layer: "server" });
const session = createBinarySession(config.value, logger);
const opened = await openInitialTarget(
session,
config.value,
serverLogger,
dependencies.writeStderr,
);
if (!opened.ok) return opened.exitCode;
const runtimeState = createRuntimeState(config.value);
const transport = await startMcpTransport(dependencies, session, {
logger,
serverLogger,
loadOptionalProviders:
dependencies.loadOptionalProviders ??
(async () => {
const [
{ CdpBrowserProvider },
{ PlaywrightBrowserScenarioProvider },
{ CdpElectronProvider },
{ PlaywrightElectronActiveProvider },
{ V8InspectorProvider },
] = await Promise.all([
import("./browser/CdpBrowserProvider.js"),
import("./browser/PlaywrightBrowserScenarioProvider.js"),
import("./browser/CdpElectronProvider.js"),
import("./browser/PlaywrightElectronActiveProvider.js"),
import("./browser/V8InspectorProvider.js"),
]);
return {
browserObservation: new CdpBrowserProvider(),
browserScenarioCapture: new PlaywrightBrowserScenarioProvider(),
electronObservation: new CdpElectronProvider(),
electronActiveObservation: new PlaywrightElectronActiveProvider(),
javascriptRuntimeObservation: new V8InspectorProvider(),
};
}),
});
if (!transport.ok) return 1;
const unregisterReload = registerConfigReload({
dependencies,
runtimeState,
serverLogger,
});
createShutdown({
handle: transport.handle,
session,
unregisterReload,
dependencies,
serverLogger,
});
return 0;
};
/** Run the executable boundary without exposing an unexpected startup cause. */
export const runEntrypoint = async (
start: () => Promise<number> = () => run(),
writeStderr: (text: string) => void = (text) => process.stderr.write(text),
setExitCode: (code: number) => void = (code) => {
process.exitCode = code;
},
): Promise<void> => {
try {
setExitCode(await start());
} catch {
writeStderr(`${SERVER_START_FAILED}\n`);
setExitCode(1);
}
};
const entryPath = process.argv[1];
if (
entryPath !== undefined &&
pathToFileURL(realpathSync(entryPath)).href === import.meta.url
) {
void runEntrypoint();
}