Fork resource subscriptions by protocol era: legacy keeps resources/subscribe; modern (2026-07-28) models subscriptions as a filter over one long-lived subscriptions/listen stream with reconnect-by-re-listen (capped backoff), an optimistic Connecting→Listening header badge, and a fix so the browser proxy no longer blocks listen() ~60s. Closes #1630.
This commit is contained in:
parent
a71e6d2012
commit
320daf2f67
1 changed files with 1656 additions and 90 deletions
|
|
@ -21,8 +21,11 @@ import type {
|
|||
ManagedResourcesState,
|
||||
ManagedResourcesStateEventMap,
|
||||
} from "./managedResourcesState.js";
|
||||
import type { InspectorResourceSubscription } from "../types.js";
|
||||
import { isTerminalStatus } from "../types.js";
|
||||
import type {
|
||||
InspectorResourceSubscription,
|
||||
ResourceSubscriptionStreamState,
|
||||
} from "../types.js";
|
||||
import { isTerminalStatus, INACTIVE_SUBSCRIPTION_STREAM_STATE } from "../types.js";
|
||||
import type { Resource } from "@modelcontextprotocol/client";
|
||||
import {
|
||||
TypedEventTarget,
|
||||
|
|
@ -31,6 +34,12 @@ import {
|
|||
|
||||
export interface ResourceSubscriptionsStateEventMap {
|
||||
subscriptionsChange: InspectorResourceSubscription[];
|
||||
/**
|
||||
* Modern-era (2026-07-28) `subscriptions/listen` stream lifecycle (#1630).
|
||||
* Mirrors the client's `resourceSubscriptionStreamChange`; `active: false` on
|
||||
* the legacy era.
|
||||
*/
|
||||
streamStateChange: ResourceSubscriptionStreamState;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -42,6 +51,8 @@ export class ResourceSubscriptionsState extends TypedEventTarget<ResourceSubscri
|
|||
private subscribedUris: string[] = [];
|
||||
private lastUpdatedByUri: Map<string, Date> = new Map();
|
||||
private subscriptions: InspectorResourceSubscription[] = [];
|
||||
private streamState: ResourceSubscriptionStreamState =
|
||||
INACTIVE_SUBSCRIPTION_STREAM_STATE;
|
||||
private client: InspectorClientProtocol | null = null;
|
||||
private resourcesState: ManagedResourcesState | null = null;
|
||||
private unsubscribe: (() => void) | null = null;
|
||||
|
|
@ -53,6 +64,17 @@ export class ResourceSubscriptionsState extends TypedEventTarget<ResourceSubscri
|
|||
super();
|
||||
this.client = client;
|
||||
this.resourcesState = resourcesState;
|
||||
this.streamState = client.getResourceSubscriptionStreamState();
|
||||
|
||||
const onStreamStateChange = (
|
||||
event: TypedEventGeneric<
|
||||
InspectorClientEventMap,
|
||||
"resourceSubscriptionStreamChange"
|
||||
>,
|
||||
): void => {
|
||||
this.streamState = event.detail;
|
||||
this.dispatchTypedEvent("streamStateChange", this.streamState);
|
||||
};
|
||||
|
||||
const onSubscriptionsChange = (
|
||||
event: TypedEventGeneric<
|
||||
|
|
@ -87,7 +109,9 @@ export class ResourceSubscriptionsState extends TypedEventTarget<ResourceSubscri
|
|||
this.subscribedUris = [];
|
||||
this.lastUpdatedByUri.clear();
|
||||
this.subscriptions = [];
|
||||
this.streamState = INACTIVE_SUBSCRIPTION_STREAM_STATE;
|
||||
this.dispatchTypedEvent("subscriptionsChange", this.getSubscriptions());
|
||||
this.dispatchTypedEvent("streamStateChange", this.streamState);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -105,6 +129,10 @@ export class ResourceSubscriptionsState extends TypedEventTarget<ResourceSubscri
|
|||
"resourceSubscriptionsChange",
|
||||
onSubscriptionsChange,
|
||||
);
|
||||
client.addEventListener(
|
||||
"resourceSubscriptionStreamChange",
|
||||
onStreamStateChange,
|
||||
);
|
||||
client.addEventListener("resourceUpdated", onResourceUpdated);
|
||||
client.addEventListener("statusChange", onStatusChange);
|
||||
resourcesState?.addEventListener("resourcesChange", onResourcesChange);
|
||||
|
|
@ -114,6 +142,10 @@ export class ResourceSubscriptionsState extends TypedEventTarget<ResourceSubscri
|
|||
"resourceSubscriptionsChange",
|
||||
onSubscriptionsChange,
|
||||
);
|
||||
this.client?.removeEventListener(
|
||||
"resourceSubscriptionStreamChange",
|
||||
onStreamStateChange,
|
||||
);
|
||||
this.client?.removeEventListener("resourceUpdated", onResourceUpdated);
|
||||
this.client?.removeEventListener("statusChange", onStatusChange);
|
||||
this.resourcesState?.removeEventListener(
|
||||
|
|
@ -129,6 +161,14 @@ export class ResourceSubscriptionsState extends TypedEventTarget<ResourceSubscri
|
|||
return [...this.subscriptions];
|
||||
}
|
||||
|
||||
/**
|
||||
* Current modern-era listen-stream state (#1630). `active: false` on the
|
||||
* legacy era, so the Resources screen can show/hide the stream chrome.
|
||||
*/
|
||||
getStreamState(): ResourceSubscriptionStreamState {
|
||||
return this.streamState;
|
||||
}
|
||||
|
||||
private rebuild(): void {
|
||||
const resources = this.resourcesState?.getResources() ?? [];
|
||||
const byUri = new Map(resources.map((r) => [r.uri, r]));
|
||||
|
|
@ -146,5 +186,6 @@ export class ResourceSubscriptionsState extends TypedEventTarget<ResourceSubscri
|
|||
this.subscribedUris = [];
|
||||
this.lastUpdatedByUri.clear();
|
||||
this.subscriptions = [];
|
||||
this.streamState = INACTIVE_SUBSCRIPTION_STREAM_STATE;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue