From b3c5672ce67564e9b7c7baa6ce21f1674e27b287 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Wed, 14 Oct 2026 10:06:46 +0800 Subject: [PATCH] fix(setup): preserve validated caller-selected JEB registration settings --- docs/jeb-analysis.md | 6 ++++ src/application/Doctor.ts | 11 ++++++++ src/application/SetupHost.ts | 1 + src/doctorRuntime.ts | 14 ++++++++-- src/setupEnvironment.test.ts | 54 ++++++++++++++++++++++++++++++++++++ 5 files changed, 84 insertions(+), 2 deletions(-) diff --git a/docs/jeb-analysis.md b/docs/jeb-analysis.md index e311491b6..601753257 100644 --- a/docs/jeb-analysis.md +++ b/docs/jeb-analysis.md @@ -10,6 +10,12 @@ Start a JEB client that serves MCP and point REA at it: - In the JEB GUI client, start the MCP server from the _File_ menu (or via the VIBRE assistant). - Headless, use JEB's documented programmatic route (`JebMcpServerInstance` from the JEB API) with the JEB client on your classpath; JEB 5.48's `-c` headless mode does not accept `--mcp` directly. +For setup registration, set `REA_JEB_MCP_URL` in the REA process environment +before running `rea setup`. The approved setup plan preserves the exact selected +URL after validating its syntax. That validation does not establish that the +JEB service is reachable or that a project is open; use the existing +`inspect-jeb-client` command for a read-only service query. + Verified against JEB 5.48.0 (Linux x64, licensed build) serving `jeb-mcp-server` 1.3.0 over streamable HTTP. ## Operations diff --git a/src/application/Doctor.ts b/src/application/Doctor.ts index 24adc5500..f014209d7 100644 --- a/src/application/Doctor.ts +++ b/src/application/Doctor.ts @@ -99,6 +99,8 @@ export interface DoctorHost { brewHopperPath(): Promise; manualHopperPaths(): Promise; providerInspections?(): Promise; + /** Validated provider settings preserved by setup, independent of readiness. */ + providerRegistrationEnvironment?(): Readonly>; installationPaths?(): Promise; installedSkillIdentity?( clientIds: readonly string[], @@ -368,6 +370,9 @@ export interface SystemDoctorHostOptions { readonly providerInspections?: () => Promise< readonly DoctorProviderInspection[] >; + readonly providerRegistrationEnvironment?: () => Readonly< + Record + >; readonly linuxDemoRuntimeCheck?: () => Promise; } @@ -478,6 +483,12 @@ export const systemDoctorHost = ( ...(options.providerInspections === undefined ? {} : { providerInspections: options.providerInspections }), + ...(options.providerRegistrationEnvironment === undefined + ? {} + : { + providerRegistrationEnvironment: + options.providerRegistrationEnvironment, + }), async ilspyCmdVersion(path) { try { await access(path, constants.X_OK); diff --git a/src/application/SetupHost.ts b/src/application/SetupHost.ts index 623202fdd..651616fd8 100644 --- a/src/application/SetupHost.ts +++ b/src/application/SetupHost.ts @@ -175,6 +175,7 @@ export const systemSetupHost = ( ...providerRegistrationEnvironment( diagnosis.providerInspections ?? [], ), + ...doctorHost.providerRegistrationEnvironment?.(), ...(diagnosis.hopperPath === undefined ? {} : { HOPPER_LAUNCHER_PATH: diagnosis.hopperPath }), diff --git a/src/doctorRuntime.ts b/src/doctorRuntime.ts index 75abfe2aa..db6ab20f4 100644 --- a/src/doctorRuntime.ts +++ b/src/doctorRuntime.ts @@ -7,14 +7,23 @@ import { fileURLToPath } from "node:url"; import { inspectJadxProvider } from "./android/JadxDoctor.js"; import { inspectSystemGhidraProvider } from "./ghidra/GhidraDoctor.js"; import { inspectIdaRegistration } from "./ida/IdaDoctor.js"; +import { parseJebMcpEndpoint } from "./jeb/JebMcpEndpoint.js"; import { selectLinuxPrivateDisplayStrategy } from "./hopper/LinuxPrivateDisplayProbe.js"; /** Compose provider diagnostics at the outer CLI adapter boundary. */ export const createSystemDoctorHost = ( environment: Readonly = process.env, -): DoctorHost => - systemDoctorHost({ +): DoctorHost => { + const selectedJebEndpoint = environment.REA_JEB_MCP_URL; + const selectedJebRegistrationEnvironment: Readonly> = + selectedJebEndpoint !== undefined && + selectedJebEndpoint.length > 0 && + parseJebMcpEndpoint(selectedJebEndpoint).ok + ? { REA_JEB_MCP_URL: selectedJebEndpoint } + : {}; + return systemDoctorHost({ environment, + providerRegistrationEnvironment: () => selectedJebRegistrationEnvironment, providerInspections: async () => [ await inspectSystemGhidraProvider(environment), inspectIdaRegistration(environment.REA_IDA_MCP_CONFIG), @@ -22,6 +31,7 @@ export const createSystemDoctorHost = ( ], linuxDemoRuntimeCheck: () => inspectLinuxPrivateDisplay(environment), }); +}; const HOPPER_DEMO_HELPER_PATH = fileURLToPath( new URL("../scripts/hopper-demo-x11.py", import.meta.url), diff --git a/src/setupEnvironment.test.ts b/src/setupEnvironment.test.ts index ce1c7d7d8..d40533656 100644 --- a/src/setupEnvironment.test.ts +++ b/src/setupEnvironment.test.ts @@ -92,6 +92,60 @@ it("composes provider diagnostics from selected configuration rather than ambien expect(JSON.stringify(inspections)).not.toContain("/ambient/"); }); +it("preserves a validated selected JEB endpoint in setup registrations without claiming live readiness", async () => { + const root = await mkdtemp(join(tmpdir(), "rea-jeb-setup-")); + const endpoint = "http://127.0.0.1:8425/mcp/"; + const ambientEndpoint = "http://127.0.0.1:8426/mcp"; + vi.stubEnv("REA_JEB_MCP_URL", ambientEndpoint); + try { + const environment = { + HOME: root, + USERPROFILE: root, + CODEX_HOME: join(root, "codex"), + REA_JEB_MCP_URL: endpoint, + }; + const doctorHost = createSystemDoctorHost(environment); + const setupHost = systemSetupHost(doctorHost, environment); + const state = await setupHost.initialSetupState(); + + expect(state.providerEnvironment).toHaveProperty( + "REA_JEB_MCP_URL", + endpoint, + ); + const planned = await runSetup( + { + ...options(false), + dryRun: true, + clientIds: ["codex"], + installSkill: false, + }, + setupHost, + ); + expect(planned.status).toBe("planned"); + expect(JSON.stringify(planned.plannedActions)).toContain(endpoint); + expect(JSON.stringify(planned.plannedActions)).not.toContain( + ambientEndpoint, + ); + + expect( + createSystemDoctorHost({}).providerRegistrationEnvironment?.(), + ).toEqual({}); + expect( + createSystemDoctorHost({ + REA_JEB_MCP_URL: "http://127.0.0.1:8425/mcp", + }).providerRegistrationEnvironment?.(), + ).toEqual({ REA_JEB_MCP_URL: "http://127.0.0.1:8425/mcp" }); + expect( + createSystemDoctorHost({ + REA_JEB_MCP_URL: "https://example.com/mcp", + }).providerRegistrationEnvironment?.(), + ).toEqual({}); + } finally { + vi.unstubAllEnvs(); + await rm(root, { recursive: true, force: true }); + } +}); + it("passes only selected environment to updater subprocesses and drops npm invocation identity", async () => { vi.stubEnv("REA_UPDATE_MARKER", "ambient"); const result = await runUpdateCommand(