mirror of
https://github.com/morluto/rea.git
synced 2026-11-10 00:36:35 +00:00
* feat(cli): expose provider selection diagnostics in session status The registry now records how each deep provider became selected and why a candidate was rejected, but the session status projection dropped both fields. CLI providers/capabilities and MCP binary_session therefore showed a candidate list that could not explain an automatic or explicit choice, so users had to re-derive the reason from prose in selection errors. Project selection_source and rejections for every candidate. Complete detail keeps the rejection remediation and diagnostics; summary keeps the stable origin, code and reason and lists the dropped fields in omitted_fields. Existing candidate fields are unchanged, so both projections stay additive. * test(javascript): distinguish package scope in route fixtures * test(apple): separate identity and refusal fixture groups * fix(session): preserve binding source in refreshed candidates --------- Co-authored-by: 2SinceJuly <202121119+2SinceJuly@users.noreply.github.com> Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
262 lines
8.0 KiB
TypeScript
262 lines
8.0 KiB
TypeScript
import { createCatalogIdentity } from "../../../src/catalogIdentity.js";
|
|
import { contractsForToolFamilies } from "../../../src/contracts/toolFamilies.js";
|
|
import { promptsForToolFamilies } from "../../../src/contracts/promptToolFamilyScope.js";
|
|
import { Cli } from "incur";
|
|
import { expect, it, vi } from "vitest";
|
|
import { runSessionStatus } from "../../../src/application/DirectAnalysisStatus.js";
|
|
import { registerUtilityCommands } from "../../../src/cli/utilityCommands.js";
|
|
import { parseConfig } from "../../../src/config/parseConfig.js";
|
|
import { isCliOperationFailure } from "../../../src/cliLogging.js";
|
|
import { ProviderCleanupError } from "../../../src/domain/providerCleanupError.js";
|
|
import { err, ok } from "../../../src/domain/result.js";
|
|
import { silentLogger } from "../../../src/logger.js";
|
|
import { createTestBinarySession } from "../../fixtures/binarySession.js";
|
|
import { observed } from "../../fixtures/analysisExecution.js";
|
|
|
|
const createStatusSession = () =>
|
|
createTestBinarySession(() => ({
|
|
close: async () => ok(null),
|
|
execute: async () => observed(null),
|
|
}));
|
|
|
|
it.each(["capabilities", "providers"])(
|
|
"reports failed %s finalization with the completed status",
|
|
async (command) => {
|
|
const session = createStatusSession();
|
|
const cleanup = new ProviderCleanupError(
|
|
"fixture",
|
|
["retained profile reader"],
|
|
{ reason: "profile reader close failed" },
|
|
{ remediationAction: "Retry the retained profile reader cleanup." },
|
|
);
|
|
const close = vi
|
|
.spyOn(session, "close")
|
|
.mockResolvedValueOnce(err(cleanup));
|
|
const cli = Cli.create({ name: "rea" });
|
|
registerUtilityCommands(
|
|
cli,
|
|
silentLogger,
|
|
{},
|
|
{
|
|
runSessionStatus: () =>
|
|
runSessionStatus({
|
|
readConfiguration: () => parseConfig({}),
|
|
createBinarySession: () => session,
|
|
}),
|
|
runProviderAnalysis: async () => ({ ok: false, error: null }),
|
|
},
|
|
);
|
|
let stdout = "";
|
|
try {
|
|
await cli.serve([command, "--json"], {
|
|
env: {},
|
|
stdout: (text) => {
|
|
stdout += text;
|
|
},
|
|
exit: () => {},
|
|
});
|
|
const output: unknown = JSON.parse(stdout);
|
|
expect(output).toMatchObject({
|
|
code: "cleanup_incomplete",
|
|
remediation: { action: "Retry the retained profile reader cleanup." },
|
|
details: {
|
|
resources: ["retained profile reader"],
|
|
partial_observation: {
|
|
capabilities: expect.any(Array),
|
|
server_identity: expect.any(Object),
|
|
},
|
|
},
|
|
});
|
|
expect(isCliOperationFailure(output)).toBe(true);
|
|
expect(close).toHaveBeenCalledTimes(1);
|
|
} finally {
|
|
close.mockRestore();
|
|
await session.close();
|
|
}
|
|
},
|
|
);
|
|
|
|
it.each([false, true])(
|
|
"keeps status failure when final cleanup also fails: %s",
|
|
async (cleanupFails) => {
|
|
const session = createStatusSession();
|
|
const failure = new Error("status observation failed");
|
|
const cleanup = new ProviderCleanupError("fixture", ["reader"], {});
|
|
const status = vi.spyOn(session, "status").mockRejectedValueOnce(failure);
|
|
const close = vi
|
|
.spyOn(session, "close")
|
|
.mockResolvedValueOnce(cleanupFails ? err(cleanup) : ok(null));
|
|
try {
|
|
const result = runSessionStatus({
|
|
readConfiguration: () => parseConfig({}),
|
|
createBinarySession: () => session,
|
|
});
|
|
if (cleanupFails)
|
|
await expect(result).rejects.toMatchObject({
|
|
cause: failure,
|
|
errors: [failure, cleanup],
|
|
});
|
|
else await expect(result).rejects.toBe(failure);
|
|
expect(close).toHaveBeenCalledTimes(1);
|
|
} finally {
|
|
status.mockRestore();
|
|
close.mockRestore();
|
|
await session.close();
|
|
}
|
|
},
|
|
);
|
|
|
|
it.each(["capabilities", "providers"])(
|
|
"reports the configured family scope through %s",
|
|
async (command) => {
|
|
const session = createStatusSession();
|
|
const families = new Set(["session"]);
|
|
const expected = createCatalogIdentity({
|
|
contracts: contractsForToolFamilies(families),
|
|
promptContracts: promptsForToolFamilies(families),
|
|
outputSchemaProfile: "compact",
|
|
});
|
|
const cli = Cli.create({ name: "rea" });
|
|
registerUtilityCommands(
|
|
cli,
|
|
silentLogger,
|
|
{},
|
|
{
|
|
runSessionStatus: () =>
|
|
runSessionStatus({
|
|
readConfiguration: () =>
|
|
parseConfig({
|
|
REA_MCP_TOOL_FAMILIES: " session,session ",
|
|
REA_MCP_INPUT_SCHEMA_PROFILE: "full",
|
|
REA_MCP_OUTPUT_SCHEMA_PROFILE: "compact",
|
|
}),
|
|
createBinarySession: () => session,
|
|
}),
|
|
runProviderAnalysis: async () => ({ ok: false, error: null }),
|
|
},
|
|
);
|
|
let stdout = "";
|
|
await cli.serve([command, "--json"], {
|
|
env: {},
|
|
stdout: (text) => {
|
|
stdout += text;
|
|
},
|
|
exit: () => {},
|
|
});
|
|
expect(JSON.parse(stdout)).toMatchObject({
|
|
server_identity: { catalog: expected },
|
|
});
|
|
},
|
|
);
|
|
|
|
it.each(["capabilities", "providers"])(
|
|
"exposes each candidate's selection source and rejections through %s",
|
|
async (command) => {
|
|
const session = createStatusSession();
|
|
const status = vi.spyOn(session, "status").mockResolvedValueOnce({
|
|
open: false,
|
|
capabilities: [],
|
|
analysis_provider_binding: null,
|
|
analysis_provider_candidates: [
|
|
{
|
|
provider: { id: "fixture", name: "Fixture", version: "1" },
|
|
availability: {
|
|
status: "available",
|
|
code: null,
|
|
reason: null,
|
|
diagnostics: {},
|
|
},
|
|
target_support: {
|
|
status: "unknown",
|
|
code: null,
|
|
reason: "No target is open.",
|
|
diagnostics: {},
|
|
},
|
|
selected: true,
|
|
selection_source: "auto-single-candidate",
|
|
rejections: [],
|
|
capabilities: [],
|
|
},
|
|
{
|
|
provider: { id: "other", name: "Other", version: null },
|
|
availability: {
|
|
status: "unavailable",
|
|
code: "not_configured",
|
|
reason: "OTHER_HOME is not set.",
|
|
remediation: "Set OTHER_HOME.",
|
|
diagnostics: { checks: [] },
|
|
},
|
|
target_support: {
|
|
status: "unknown",
|
|
code: null,
|
|
reason: "No target is open.",
|
|
diagnostics: {},
|
|
},
|
|
selected: false,
|
|
selection_source: null,
|
|
rejections: [
|
|
{
|
|
provider_id: "other",
|
|
origin: "availability",
|
|
code: "not_configured",
|
|
reason: "OTHER_HOME is not set.",
|
|
remediation: "Set OTHER_HOME.",
|
|
diagnostics: { checks: [] },
|
|
},
|
|
],
|
|
capabilities: [],
|
|
},
|
|
],
|
|
});
|
|
const cli = Cli.create({ name: "rea" });
|
|
registerUtilityCommands(
|
|
cli,
|
|
silentLogger,
|
|
{},
|
|
{
|
|
runSessionStatus: () =>
|
|
runSessionStatus({
|
|
readConfiguration: () => parseConfig({}),
|
|
createBinarySession: () => session,
|
|
}),
|
|
runProviderAnalysis: async () => ({ ok: false, error: null }),
|
|
},
|
|
);
|
|
let stdout = "";
|
|
try {
|
|
await cli.serve([command, "--json"], {
|
|
env: {},
|
|
stdout: (text) => {
|
|
stdout += text;
|
|
},
|
|
exit: () => {},
|
|
});
|
|
expect(JSON.parse(stdout)).toMatchObject({
|
|
analysis_provider_candidates: [
|
|
{
|
|
provider: { id: "fixture" },
|
|
selection_source: "auto-single-candidate",
|
|
rejections: [],
|
|
},
|
|
{
|
|
provider: { id: "other" },
|
|
selection_source: null,
|
|
rejections: [
|
|
{
|
|
provider_id: "other",
|
|
origin: "availability",
|
|
code: "not_configured",
|
|
reason: "OTHER_HOME is not set.",
|
|
remediation: "Set OTHER_HOME.",
|
|
},
|
|
],
|
|
},
|
|
],
|
|
});
|
|
} finally {
|
|
status.mockRestore();
|
|
await session.close();
|
|
}
|
|
},
|
|
);
|