mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
fix(jeb): attribute transport timeouts to requested operations
Project canonical timeout and cancellation facts at the provider boundary while preserving causes, partial observations, live cleanup dependencies and provider-authored deadline guidance. Validation: four real SDK/HTTP catalog and tool timeout controls fail before the fix. After rebasing onto the Ghidra timeout guidance change, 189 tests across ten files pass, including public CLI/MCP workflows and affected Ghidra tests. Typecheck/lint, docs, format and discovery pass. No native JEB engine or target verification. Refs #4597.
This commit is contained in:
1 parent
a6e8f5269f
commit
d74e72406a
3 files changed
+107
-3
No files matched your search
@@ -11,6 +11,7 @@ import {
|
||||
AnalysisInputError,
|
||||
AnalysisProtocolError,
|
||||
AnalysisCancelledError,
|
||||
AnalysisTimeoutError,
|
||||
} from "../domain/analysisErrorCore.js";
|
||||
import type { JsonValue } from "../domain/jsonValue.js";
|
||||
import { jebResultSchemas } from "../domain/jeb/jebAnalysis.js";
|
||||
@@ -490,3 +491,29 @@ it("keeps concurrent client inspections bound to their own parsed metadata", asy
|
||||
await provider.close();
|
||||
}
|
||||
});
|
||||
|
||||
it("preserves provider-authored timeout guidance when projecting the requested operation", async () => {
|
||||
const reason = new Error("selected deadline elapsed");
|
||||
const timeout = new AnalysisTimeoutError("list_units", 123, {
|
||||
cause: reason,
|
||||
userMessage: "The selected MCP request budget elapsed.",
|
||||
remediationAction: "Increase the selected MCP request budget.",
|
||||
});
|
||||
const provider = providerWith(scriptedConnection({ list_units: timeout }));
|
||||
try {
|
||||
const result = await provider.execute({
|
||||
operation: "list_jeb_units",
|
||||
input: { count: 2, index: 0 },
|
||||
});
|
||||
if (result.ok) throw new Error("Timed-out request passed");
|
||||
expect(result.error).toMatchObject({
|
||||
operation: "list_jeb_units",
|
||||
timeoutMs: 123,
|
||||
userMessage: timeout.userMessage,
|
||||
remediationAction: timeout.remediationAction,
|
||||
});
|
||||
expect(result.error.cause).toBe(reason);
|
||||
} finally {
|
||||
await provider.close();
|
||||
}
|
||||
});
|
||||
+21
-3
@@ -20,6 +20,7 @@ import {
|
||||
AnalysisCancelledError,
|
||||
AnalysisCapabilityUnavailableError,
|
||||
AnalysisProtocolError,
|
||||
AnalysisTimeoutError,
|
||||
AnalysisUnsupportedTargetError,
|
||||
} from "../domain/analysisErrorCore.js";
|
||||
import { AnalysisError } from "../domain/analysisErrorBase.js";
|
||||
@@ -425,16 +426,33 @@ export class JebProvider implements JebAnalysisPort {
|
||||
)
|
||||
.bindCleanupDependencies([cause.cleanupFailure])
|
||||
.bindCleanupResourceOwner(cause.cleanupFailure);
|
||||
if (cause instanceof AnalysisCancelledError)
|
||||
return new AnalysisCancelledError(operation, {
|
||||
if (
|
||||
cause instanceof AnalysisCancelledError ||
|
||||
cause instanceof AnalysisTimeoutError
|
||||
) {
|
||||
const options = {
|
||||
cause: cause.cause,
|
||||
diagnostics: cause.diagnostics,
|
||||
capturedOutput: cause.capturedOutput,
|
||||
cleanup: cause.cleanup,
|
||||
partialObservation: cause.partialObservation,
|
||||
})
|
||||
};
|
||||
const failure =
|
||||
cause instanceof AnalysisTimeoutError
|
||||
? new AnalysisTimeoutError(operation, cause.timeoutMs, {
|
||||
...options,
|
||||
...(cause.userMessage === undefined
|
||||
? {}
|
||||
: { userMessage: cause.userMessage }),
|
||||
...(cause.remediationAction === undefined
|
||||
? {}
|
||||
: { remediationAction: cause.remediationAction }),
|
||||
})
|
||||
: new AnalysisCancelledError(operation, options);
|
||||
return failure
|
||||
.bindCleanupDependencies([cause])
|
||||
.bindCleanupResourceOwner(cause);
|
||||
}
|
||||
if (cause instanceof AnalysisError) return cause;
|
||||
if (cause instanceof z.ZodError)
|
||||
return new AnalysisProtocolError(
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { expect, it } from "vitest";
|
||||
import { JebProvider } from "../../../src/jeb/JebProvider.js";
|
||||
import { createStreamableHttpJebMcpConnection } from "../../../src/jeb/JebMcpConnection.js";
|
||||
import { AnalysisTimeoutError } from "../../../src/domain/analysisErrorCore.js";
|
||||
import {
|
||||
startJebMcpFixture,
|
||||
jebPrimaryResponses,
|
||||
} from "../../fixtures/jebMcp.js";
|
||||
|
||||
it.each([
|
||||
{ operation: "inspect_jeb_client", stage: "catalog" },
|
||||
{ operation: "inspect_jeb_client", stage: "tool" },
|
||||
{ operation: "list_jeb_units", stage: "catalog" },
|
||||
{ operation: "list_jeb_units", stage: "tool" },
|
||||
] as const)(
|
||||
"attributes a real SDK $stage timeout to $operation",
|
||||
async ({ operation, stage }) => {
|
||||
let release!: () => void;
|
||||
const gate = new Promise<void>((resolve) => {
|
||||
release = resolve;
|
||||
});
|
||||
const fixture = await startJebMcpFixture(
|
||||
async (tool) => {
|
||||
await gate;
|
||||
return jebPrimaryResponses[tool]!;
|
||||
},
|
||||
stage === "catalog"
|
||||
? async () => {
|
||||
await gate;
|
||||
return { tools: [] };
|
||||
}
|
||||
: undefined,
|
||||
);
|
||||
const provider = new JebProvider({
|
||||
environment: { REA_JEB_MCP_URL: fixture.endpoint },
|
||||
connectionFactory: (endpoint) =>
|
||||
createStreamableHttpJebMcpConnection(endpoint, {
|
||||
discoveryTimeoutMs: 100,
|
||||
requestTimeoutMs: 100,
|
||||
}),
|
||||
});
|
||||
try {
|
||||
const result = await provider.execute(
|
||||
operation === "inspect_jeb_client"
|
||||
? { operation, input: {} }
|
||||
: { operation, input: { count: 2, index: 0 } },
|
||||
);
|
||||
if (result.ok) throw new Error("Timed-out MCP request passed");
|
||||
expect(result.error).toBeInstanceOf(AnalysisTimeoutError);
|
||||
expect(result.error).toMatchObject({ operation, timeoutMs: 100 });
|
||||
expect(result.error.cause).toBeDefined();
|
||||
expect(result.error.cleanupIncomplete).toBe(false);
|
||||
} finally {
|
||||
release();
|
||||
await provider.close();
|
||||
await fixture.close();
|
||||
}
|
||||
},
|
||||
);
|
||||
Reference in new issue
Block a user