mirror of
https://github.com/morluto/rea
synced 2026-10-25 22:08:11 -04:00
fix(mcp): preserve migrated revisions and valid links
This commit is contained in:
@@ -398,8 +398,8 @@ const migrateLegacyWorkspace = (input: unknown): InvestigationWorkspace => {
|
||||
const migrated = buildWorkspace({
|
||||
workspaceId: parsed.workspace_id,
|
||||
name: parsed.name,
|
||||
revision: parsed.revision + 1,
|
||||
previousRevisionDigest: parsed.revision_digest,
|
||||
revision: parsed.revision,
|
||||
previousRevisionDigest: parsed.previous_revision_digest,
|
||||
bundle,
|
||||
runs: runs.map((run) => ({
|
||||
...run,
|
||||
|
||||
@@ -116,7 +116,9 @@ const registerEnhancedTool = (
|
||||
recordUnknown: registration.recordUnknown,
|
||||
});
|
||||
if (!unknowns.ok) return toCallToolResult(unknowns, contract);
|
||||
return toCallToolResult({ ok: true, value: evidence }, contract);
|
||||
return toCallToolResult({ ok: true, value: evidence }, contract, {
|
||||
evidenceResourcesAvailable: recorded !== undefined,
|
||||
});
|
||||
}
|
||||
return toCallToolResult(result, contract);
|
||||
},
|
||||
|
||||
@@ -102,7 +102,9 @@ export const registerEvidenceTools = (
|
||||
const recorded = options.recordEvidence?.(evidence);
|
||||
return recorded !== undefined && !recorded.ok
|
||||
? toCallToolResult(recorded, contract)
|
||||
: toCallToolResult({ ok: true, value: evidence }, contract);
|
||||
: toCallToolResult({ ok: true, value: evidence }, contract, {
|
||||
evidenceResourcesAvailable: recorded !== undefined,
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -103,7 +103,9 @@ const registerOfficialTool = (
|
||||
const recorded = registration.recordEvidence?.(evidence);
|
||||
if (recorded !== undefined && !recorded.ok)
|
||||
return toCallToolResult(recorded, contract);
|
||||
return toCallToolResult({ ok: true, value: evidence }, contract);
|
||||
return toCallToolResult({ ok: true, value: evidence }, contract, {
|
||||
evidenceResourcesAvailable: recorded !== undefined,
|
||||
});
|
||||
}
|
||||
if (
|
||||
result.error instanceof AnalysisCapabilityUnavailableError &&
|
||||
|
||||
@@ -9,6 +9,10 @@ import type { Result } from "../domain/result.js";
|
||||
import type { JsonValue } from "../domain/jsonValue.js";
|
||||
import type { ToolContract } from "../contracts/toolContracts.js";
|
||||
|
||||
interface ToolResultOptions {
|
||||
readonly evidenceResourcesAvailable?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate an application result into MCP text content.
|
||||
* Error tags and safe messages remain visible while underlying causes, process
|
||||
@@ -17,8 +21,11 @@ import type { ToolContract } from "../contracts/toolContracts.js";
|
||||
export const toCallToolResult = (
|
||||
result: Result<JsonValue, AnalysisError>,
|
||||
contract: ToolContract,
|
||||
options: ToolResultOptions = {},
|
||||
): CallToolResult =>
|
||||
result.ok ? successResult(result.value, contract) : errorResult(result.error);
|
||||
result.ok
|
||||
? successResult(result.value, contract, options)
|
||||
: errorResult(result.error);
|
||||
|
||||
const errorResult = (error: AnalysisError): CallToolResult => {
|
||||
const projected = projectAnalysisError(error);
|
||||
@@ -37,6 +44,7 @@ const errorResult = (error: AnalysisError): CallToolResult => {
|
||||
const successResult = (
|
||||
value: JsonValue,
|
||||
contract: ToolContract,
|
||||
options: ToolResultOptions,
|
||||
): CallToolResult => {
|
||||
const candidate = contract.kind === "session" ? { result: value } : value;
|
||||
const parsed = contract.outputSchema.safeParse(candidate);
|
||||
@@ -62,14 +70,22 @@ const successResult = (
|
||||
return {
|
||||
content: [
|
||||
{ type: "text", text: formatValue(value) },
|
||||
...evidenceResourceLinks(value),
|
||||
...evidenceResourceLinks(
|
||||
value,
|
||||
contract.kind === "session" ||
|
||||
options.evidenceResourcesAvailable === true,
|
||||
),
|
||||
],
|
||||
structuredContent: candidate,
|
||||
};
|
||||
};
|
||||
|
||||
const evidenceResourceLinks = (value: JsonValue): CallToolResult["content"] => {
|
||||
const evidenceResourceLinks = (
|
||||
value: JsonValue,
|
||||
available: boolean,
|
||||
): CallToolResult["content"] => {
|
||||
if (
|
||||
!available ||
|
||||
typeof value !== "object" ||
|
||||
value === null ||
|
||||
Array.isArray(value) ||
|
||||
|
||||
@@ -155,8 +155,8 @@ describe("persistent cross-version investigation workspace", () => {
|
||||
});
|
||||
|
||||
expect(migrated).toMatchObject({
|
||||
revision: current.revision + 1,
|
||||
previous_revision_digest: legacyDigest,
|
||||
revision: current.revision,
|
||||
previous_revision_digest: current.previous_revision_digest,
|
||||
runs: [
|
||||
{
|
||||
legacy_request_identity: true,
|
||||
@@ -174,9 +174,11 @@ describe("persistent cross-version investigation workspace", () => {
|
||||
throw new TypeError("legacy fixture is not canonical");
|
||||
await writeFile(input.workspace_path, encodedLegacy, { mode: 0o600 });
|
||||
await expect(
|
||||
runCrossVersionInvestigation(input, policy(directory), {
|
||||
inputRoots: [directory],
|
||||
}),
|
||||
runCrossVersionInvestigation(
|
||||
{ ...input, expected_workspace_revision: current.revision },
|
||||
policy(directory),
|
||||
{ inputRoots: [directory] },
|
||||
),
|
||||
).resolves.toMatchObject({ ok: true, value: { reused: true } });
|
||||
await expect(
|
||||
runCrossVersionInvestigation(
|
||||
|
||||
@@ -164,6 +164,11 @@ describe("official Hopper proxy tools", () => {
|
||||
arguments: VALID_INPUTS[contract.name],
|
||||
});
|
||||
expect(result.isError).not.toBe(true);
|
||||
expect(result.content).not.toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ type: "resource_link" }),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
expect(invocations.map(({ name }) => name)).toEqual(
|
||||
|
||||
@@ -61,7 +61,7 @@ describe("tool result projection", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("links successful evidence to its session resource URI", () => {
|
||||
it("links successful evidence only when its session resource exists", () => {
|
||||
const evidence = createEvidence(
|
||||
undefined,
|
||||
{ id: "fixture", name: "Fixture", version: "1" },
|
||||
@@ -79,6 +79,15 @@ describe("tool result projection", () => {
|
||||
|
||||
expect(
|
||||
toCallToolResult(ok(evidence), evidenceContract).content,
|
||||
).not.toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ type: "resource_link" }),
|
||||
]),
|
||||
);
|
||||
expect(
|
||||
toCallToolResult(ok(evidence), evidenceContract, {
|
||||
evidenceResourcesAvailable: true,
|
||||
}).content,
|
||||
).toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: "resource_link",
|
||||
|
||||
Reference in New Issue
Block a user