fix(jeb): preserve selected endpoint in failures (#7835)

* fix(jeb): preserve selected endpoint in failures

* fix(jeb): retain endpoint on typed failures
This commit is contained in:
morluto authored and GitHub committed 2026-10-29 01:30:24 +08:00
1 parent 128c2cf632
commit e79ad6ca8e
3 files changed
+188 -9

No files matched your search

+93
View File
@@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
import { createJebAnalysisProvider } from "../composition/jeb.js";
import { JebProvider } from "./JebProvider.js";
import { decodeJebToolResult } from "./JebMcpConnection.js";
import type {
JebMcpConnection,
JebMcpConnectionTimeouts,
@@ -52,6 +53,40 @@ const providerWith = (connection: JebMcpConnection) =>
});
describe("JebProvider live availability", () => {
it("retains the caller-selected endpoint spelling in results and failures", async () => {
const endpoint = "HTTP://127.0.0.1:08425/mcp";
const provider = new JebProvider({
environment: { REA_JEB_MCP_URL: endpoint },
connectionFactory: () =>
scriptedConnection({
get_client_information: { success: true, version: "5.48.0" },
list_units: new Error("connection closed"),
}),
});
try {
expect((await provider.inspectAvailability()).diagnostics).toMatchObject({
endpoint,
});
const observation = await provider.execute({
operation: "inspect_jeb_client",
input: {},
});
if (!observation.ok) throw observation.error;
expect(
jebResultSchemas.inspect_jeb_client.parse(observation.value.result)
.engine.endpoint,
).toBe(endpoint);
const failure = await provider.execute({
operation: "list_jeb_units",
input: { count: 10, index: 0 },
});
if (failure.ok) throw new Error("Expected transport failure");
expect(failure.error.diagnostics).toMatchObject({ endpoint });
} finally {
await provider.close();
}
});
it("passes its validated environment timeout to the production connection", async () => {
let configured: JebMcpConnectionTimeouts | undefined;
const provider = createJebAnalysisProvider(
@@ -125,6 +160,59 @@ describe("JebProvider live availability", () => {
});
});
describe("JebProvider endpoint diagnostics", () => {
it("retains the selected endpoint on malformed MCP results without replacing the protocol failure", async () => {
let failure: unknown;
try {
decodeJebToolResult({ content: [] });
} catch (cause) {
failure = cause;
}
if (!(failure instanceof AnalysisProtocolError))
throw new Error("Expected a protocol failure");
failure.retainDiagnostics({ phase: "decode" });
const connection = scriptedConnection({ get_client_information: failure });
const provider = providerWith(connection);
try {
const result = await provider.execute({
operation: "inspect_jeb_client",
input: {},
});
if (result.ok) throw new Error("Expected a malformed response failure");
expect(result.error).toBe(failure);
expect(result.error.diagnostics).toEqual({
phase: "decode",
endpoint: "http://127.0.0.1:8425/mcp",
});
} finally {
await provider.close();
}
});
it("retains the selected endpoint when cancelled before execution", async () => {
const connection = scriptedConnection({});
const provider = providerWith(connection);
const controller = new AbortController();
const reason = new Error("caller stopped");
controller.abort(reason);
try {
const result = await provider.execute(
{ operation: "inspect_jeb_client", input: {} },
{ signal: controller.signal },
);
if (result.ok) throw new Error("Expected cancellation");
expect(result.error).toBeInstanceOf(AnalysisCancelledError);
expect(result.error.cause).toBe(reason);
expect(result.error.diagnostics).toEqual({
endpoint: "http://127.0.0.1:8425/mcp",
});
expect(connection.calls).toEqual([]);
} finally {
await provider.close();
}
});
});
describe("JebProvider observations and failures", () => {
it("normalizes client information and caches engine identity", async () => {
const connection = scriptedConnection({
@@ -170,6 +258,9 @@ describe("JebProvider observations and failures", () => {
if (result.ok) return;
expect(result.error).toBeInstanceOf(AnalysisCapabilityUnavailableError);
expect(result.error.message).toContain("ECONNREFUSED");
expect(projectAnalysisError(result.error)).toMatchObject({
details: { diagnostics: { endpoint: "http://127.0.0.1:8425/mcp" } },
});
});
it("maps transport failures during an operation to provider unavailability", async () => {
@@ -508,6 +599,7 @@ it("preserves provider-authored timeout guidance when projecting the requested o
const reason = new Error("selected deadline elapsed");
const timeout = new AnalysisTimeoutError("list_units", 123, {
cause: reason,
diagnostics: { phase: "request" },
userMessage: "The selected MCP request budget elapsed.",
remediationAction: "Increase the selected MCP request budget.",
});
@@ -523,6 +615,7 @@ it("preserves provider-authored timeout guidance when projecting the requested o
timeoutMs: 123,
userMessage: timeout.userMessage,
remediationAction: timeout.remediationAction,
diagnostics: { phase: "request", endpoint: "http://127.0.0.1:8425/mcp" },
});
expect(result.error.cause).toBe(reason);
} finally {
+19 -9
View File
@@ -159,6 +159,8 @@ const parseJebEnvelope = (
export class JebProvider implements JebAnalysisPort {
readonly #connection: JebMcpConnection;
readonly #endpoint: URL;
/** Caller-selected spelling, retained only after endpoint admission succeeds. */
readonly #selectedEndpoint: string;
#clientInformation:
| Pick<z.infer<typeof clientInformation>, "version" | "gui_client">
| undefined;
@@ -175,6 +177,8 @@ export class JebProvider implements JebAnalysisPort {
const endpoint = parseJebMcpEndpoint(environment.REA_JEB_MCP_URL);
if (!endpoint.ok) throw endpoint.error;
this.#endpoint = endpoint.value;
this.#selectedEndpoint =
environment.REA_JEB_MCP_URL || DEFAULT_JEB_MCP_ENDPOINT;
const createConnection =
options.connectionFactory ?? createStreamableHttpJebMcpConnection;
const timeouts = {
@@ -208,7 +212,7 @@ export class JebProvider implements JebAnalysisPort {
Object.keys(JEB_ENGINE_TOOLS) as JebOperation[]
).filter((operation) => tools.has(JEB_ENGINE_TOOLS[operation])),
diagnostics: {
endpoint: this.#endpoint.toString(),
endpoint: this.#selectedEndpoint,
engine_version: information.version ?? null,
},
};
@@ -221,8 +225,8 @@ export class JebProvider implements JebAnalysisPort {
code: "runtime_unavailable",
reason: projected.message,
diagnostics: {
endpoint: this.#endpoint.toString(),
...cause.diagnostics,
endpoint: this.#selectedEndpoint,
},
};
}
@@ -230,7 +234,7 @@ export class JebProvider implements JebAnalysisPort {
status: "unavailable",
code: "not_configured",
reason: `${cause instanceof Error ? cause.message : "The JEB MCP endpoint did not respond"}. ${JEB_CONNECTION_REMEDIATION}`,
diagnostics: { endpoint: this.#endpoint.toString() },
diagnostics: { endpoint: this.#selectedEndpoint },
};
}
}
@@ -247,6 +251,7 @@ export class JebProvider implements JebAnalysisPort {
return err(
new AnalysisCancelledError(request.operation, {
cause: options.signal.reason,
diagnostics: { endpoint: this.#selectedEndpoint },
}),
);
try {
@@ -300,7 +305,7 @@ export class JebProvider implements JebAnalysisPort {
return {
name: "jeb",
version: information.version ?? null,
endpoint: this.#endpoint.toString(),
endpoint: this.#selectedEndpoint,
gui_client: information.gui_client ?? null,
};
}
@@ -375,7 +380,7 @@ export class JebProvider implements JebAnalysisPort {
engine: {
name: "jeb",
version: information.version ?? null,
endpoint: this.#endpoint.toString(),
endpoint: this.#selectedEndpoint,
gui_client: information.gui_client ?? null,
},
startup_ts: information.startup_ts ?? null,
@@ -524,7 +529,7 @@ export class JebProvider implements JebAnalysisPort {
const parsed = parseJebEnvelope(
payload,
operation,
this.#endpoint.toString(),
this.#selectedEndpoint,
input,
);
if (!parsed.ok) throw parsed.error;
@@ -546,7 +551,10 @@ export class JebProvider implements JebAnalysisPort {
) {
const options = {
cause: cause.cause,
diagnostics: cause.diagnostics,
diagnostics: {
...cause.diagnostics,
endpoint: this.#selectedEndpoint,
},
capturedOutput: cause.capturedOutput,
cleanup: cause.cleanup,
partialObservation: cause.partialObservation,
@@ -567,11 +575,12 @@ export class JebProvider implements JebAnalysisPort {
.bindCleanupDependencies([cause])
.bindCleanupResourceOwner(cause);
}
if (cause instanceof AnalysisError) return cause;
if (cause instanceof AnalysisError)
return cause.retainDiagnostics({ endpoint: this.#selectedEndpoint });
if (cause instanceof z.ZodError)
return new AnalysisProtocolError(
"JEB omitted or malformed required observation fields",
{ cause },
{ cause, diagnostics: { endpoint: this.#selectedEndpoint } },
);
const physical =
cause instanceof JebMcpPhysicalFailure ? cause.cause : cause;
@@ -581,6 +590,7 @@ export class JebProvider implements JebAnalysisPort {
physical instanceof Error ? physical.message : String(physical),
{
cause,
diagnostics: { endpoint: this.#selectedEndpoint },
userMessage: JEB_CONNECTION_REMEDIATION,
},
);
@@ -1,6 +1,8 @@
import { Client } from "@modelcontextprotocol/client";
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
import { resolve } from "node:path";
import { createServer } from "node:http";
import { once } from "node:events";
import { expect } from "vitest";
import { parseMcpToolError } from "../../fixtures/mcpToolError.js";
@@ -26,6 +28,80 @@ const isolatedEnvironment = (workspace: TestWorkspace) => ({
REA_JEB_MCP_TIMEOUT_MS: "1.5",
});
test("retains the selected endpoint when a direct JEB connection fails", async ({
cli,
workspace,
}) => {
const server = createServer((_request, response) => {
response.writeHead(404, { "content-type": "text/plain" });
response.end("not found");
});
server.listen(0, "127.0.0.1");
await once(server, "listening");
try {
const address = server.address();
if (address === null || typeof address === "string")
throw new Error("Missing loopback listener address");
const endpoint = `HTTP://127.0.0.1:0${address.port}/mcp`;
const result = await cli.run({
arguments: ["inspect-jeb-client", "--json"],
environment: {
...isolatedEnvironment(workspace),
REA_JEB_MCP_TIMEOUT_MS: "3000",
REA_JEB_MCP_URL: endpoint,
},
});
expect(result.exitCode, result.stderr).toBe(1);
expect(result.json).toMatchObject({
code: "capability_unavailable",
details: {
diagnostics: { endpoint },
reason: expect.stringContaining("not found"),
},
});
const transport = new StdioClientTransport({
command: process.execPath,
args: [resolve("scripts/rea.mjs"), "mcp"],
cwd: workspace.root,
env: {
...isolatedEnvironment(workspace),
REA_JEB_MCP_TIMEOUT_MS: "3000",
REA_JEB_MCP_URL: endpoint,
},
stderr: "pipe",
});
const client = new Client({
name: "jeb-endpoint-acceptance",
version: "1",
});
try {
await client.connect(transport);
const definition = (await client.listTools()).tools.find(
({ name }) => name === "inspect_jeb_client",
);
if (definition === undefined) throw new Error("Missing JEB tool");
const refusal = parseMcpToolError(
await client.callTool(
{ name: "inspect_jeb_client", arguments: {} },
{ toolDefinition: definition },
),
);
expect(refusal.error).toMatchObject({
code: "capability_unavailable",
details: { diagnostics: { endpoint } },
});
} finally {
await client.close();
await transport.close();
}
} finally {
server.closeAllConnections();
await new Promise<void>((resolveClose, rejectClose) =>
server.close((error) => (error ? rejectClose(error) : resolveClose())),
);
}
});
test("keeps compiled CLI discovery usable while JEB rejects its malformed timeout", async ({
cli,
workspace,