mirror of
https://github.com/morluto/rea.git
synced 2026-11-10 08:47:00 +00:00
feat(mcp): refresh provider candidates without switching (#8487)
* fix(mcp): report provider candidate observation scope * feat(mcp): refresh provider candidates without switching * docs: clarify candidate inspection ownership * fix(mcp): clarify provider reselection on reopen * test(mcp): bound candidate refresh lifecycle gates * test(mcp): keep temporary fixture cleanup with its owner
This commit is contained in:
1 parent
1a0e2c77f2
commit
0b028cc75a
12 files changed
+516
-10
No files matched your search
@@ -1304,8 +1304,20 @@ export class BinarySession
|
||||
options: {
|
||||
readonly signal?: AbortSignal;
|
||||
readonly detail?: BinarySessionDetail;
|
||||
readonly refreshCandidates?: boolean;
|
||||
} = {},
|
||||
): Promise<JsonValue> {
|
||||
return options.refreshCandidates === true
|
||||
? this.#serialize(() => this.#status(options), { abortPrompts: false })
|
||||
: this.#status(options);
|
||||
}
|
||||
|
||||
/** Keep an explicit candidate refresh ordered with target transitions. */
|
||||
#status(options: {
|
||||
readonly signal?: AbortSignal;
|
||||
readonly detail?: BinarySessionDetail;
|
||||
readonly refreshCandidates?: boolean;
|
||||
}): Promise<JsonValue> {
|
||||
const resourceRun = this.#activeBundleResources;
|
||||
const run =
|
||||
this.#active ??
|
||||
@@ -1606,9 +1618,11 @@ export class BinarySession
|
||||
|
||||
#serialize<T>(
|
||||
operation: (admittedThrough: number) => Promise<T>,
|
||||
options: { readonly abortPrompts?: boolean } = {},
|
||||
): Promise<T> {
|
||||
for (const controller of this.#promptCompletionControllers)
|
||||
controller.abort();
|
||||
if (options.abortPrompts !== false)
|
||||
for (const controller of this.#promptCompletionControllers)
|
||||
controller.abort();
|
||||
this.#transitionPending += 1;
|
||||
// Later calls wait for this transition and must not be included in its drain.
|
||||
const admittedThrough = this.#transitionGeneration++;
|
||||
|
||||
@@ -166,6 +166,7 @@ it("opens a resource-only bundle without executable identity and keeps snapshot
|
||||
expect(await session.status()).toMatchObject({
|
||||
open: true,
|
||||
session_kind: "bundle_resources",
|
||||
analysis_provider_candidates_observed: "not_applicable",
|
||||
resource_root: { canonical_path: opened.value.canonicalPath },
|
||||
});
|
||||
const snapshot = session.exportAnalysisSnapshot();
|
||||
|
||||
@@ -140,6 +140,7 @@ export interface BinarySessionPort
|
||||
status(options?: {
|
||||
readonly signal?: AbortSignal;
|
||||
readonly detail?: BinarySessionDetail;
|
||||
readonly refreshCandidates?: boolean;
|
||||
}): Promise<JsonValue>;
|
||||
activeTarget(): BinaryTarget | undefined;
|
||||
/** Read a snapshot with failed descriptors retained for session cleanup retry. */
|
||||
|
||||
@@ -47,6 +47,7 @@ interface BinarySessionStatusInput {
|
||||
readonly cleanupRetry?: AnalysisCleanupRetry;
|
||||
readonly detail?: BinarySessionDetail;
|
||||
readonly signal?: AbortSignal;
|
||||
readonly refreshCandidates?: boolean;
|
||||
}
|
||||
|
||||
/** Project internal provider routing state into the caller-visible session status. */
|
||||
@@ -66,6 +67,7 @@ export const binarySessionStatus = async ({
|
||||
cleanupRetry,
|
||||
detail = "complete",
|
||||
signal,
|
||||
refreshCandidates = false,
|
||||
}: BinarySessionStatusInput): Promise<JsonValue> => {
|
||||
const routeCapabilities = (
|
||||
bundleResourceRoot === undefined
|
||||
@@ -119,7 +121,13 @@ export const binarySessionStatus = async ({
|
||||
const analysisProviderCandidates =
|
||||
bundleResourceRoot !== undefined
|
||||
? []
|
||||
: (await router.candidateStatuses(route, signal)).map((candidate) =>
|
||||
: (
|
||||
await router.candidateStatuses(route, {
|
||||
...(signal === undefined ? {} : { signal }),
|
||||
...(target === undefined ? {} : { target }),
|
||||
refresh: refreshCandidates,
|
||||
})
|
||||
).map((candidate) =>
|
||||
detail === "summary"
|
||||
? {
|
||||
provider: providerSummary(candidate.provider),
|
||||
@@ -153,6 +161,12 @@ export const binarySessionStatus = async ({
|
||||
capabilities,
|
||||
analysis_provider_binding: analysisProviderBinding,
|
||||
analysis_provider_candidates: analysisProviderCandidates,
|
||||
analysis_provider_candidates_observed:
|
||||
bundleResourceRoot !== undefined
|
||||
? "not_applicable"
|
||||
: route.selection === undefined || refreshCandidates
|
||||
? "status_request"
|
||||
: "target_selection",
|
||||
analysis_run:
|
||||
runId === undefined
|
||||
? null
|
||||
|
||||
@@ -129,11 +129,26 @@ export class SessionProviderRouter {
|
||||
);
|
||||
}
|
||||
|
||||
/** Candidate status authoritative for the active or target-free route. */
|
||||
/** Preserve selection observations unless a caller requests a fresh inspection. */
|
||||
async candidateStatuses(
|
||||
route: SessionProviderRoute,
|
||||
signal?: AbortSignal,
|
||||
options: {
|
||||
readonly signal?: AbortSignal;
|
||||
readonly target?: BinaryTarget;
|
||||
readonly refresh?: boolean;
|
||||
} = {},
|
||||
): Promise<readonly AnalysisProviderCandidateStatus[]> {
|
||||
const { signal, target, refresh = false } = options;
|
||||
if (refresh && route.selection !== undefined) {
|
||||
const current = await this.registry.candidates(
|
||||
target,
|
||||
signal === undefined ? {} : { signal },
|
||||
);
|
||||
return current.map((candidate) => ({
|
||||
...candidate,
|
||||
selected: candidate.provider.id === route.binding?.identity.id,
|
||||
}));
|
||||
}
|
||||
return (
|
||||
route.selection?.candidates ??
|
||||
this.registry.candidates(
|
||||
|
||||
Reference in new issue
Block a user