openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
631 lines (630 loc) • 25.8 kB
JavaScript
import { a as runInDetachedAsyncContext, t as AsyncWorkScope } from "./async-work-scope-BYeX3gh6.mjs";
import { u as readDatabasePathIdentitySync } from "./sqlite-worker-identity-KSR14F6C.mjs";
import { h as createOpenClawAgentDatabaseClaim } from "./openclaw-agent-db-validation-cache-_HDeZNQr.mjs";
import { n as normalizeAgentId } from "./agent-id-D1-Q6vR4.mjs";
import { s as registerOpenClawAgentDatabaseSyncResource } from "./openclaw-agent-db-resources-x2MmUEok.mjs";
import { M as parseAgentSessionKey } from "./session-key-C09ow4h0.mjs";
import { i as isIncognitoOpenClawAgentSqlitePath } from "./openclaw-agent-db.paths-DVbbFaej.mjs";
import { n as sessionChanges } from "./session-row-changes-BcJIdta8.mjs";
import { p as isIncognitoOpenClawAgentDatabase, t as cache, y as retainAgentDatabase } from "./openclaw-agent-db-lifecycle-BJiYtXuW.mjs";
import { c as captureSessionStoreReadCandidate } from "./session-sqlite-target-Cnk9g9d0.mjs";
import { t as CONTROL_UI_SESSION_PULL_REQUESTS_CHANGED_EVENT } from "./control-ui-contract-B_YMMx3J.mjs";
import { n as prepareControlUiSessionPrServiceTarget } from "./control-ui-session-pr-read-DMkUt6uE.mjs";
import path from "node:path";
import pLimit from "p-limit";
//#region src/gateway/control-ui-session-pr-source.ts
/** Retain source lifetime through the caller's last assertion and publication. */
async function withControlUiSessionPrSource(source, operation) {
const target = {
agentId: normalizeAgentId(source.agentId),
path: path.resolve(source.path)
};
const unregister = [];
let active = true;
let releaseNative = () => {};
const changed = () => /* @__PURE__ */ new Error("Session PR source changed or closed. Retry the request.");
try {
let paths;
let assertSource;
let sourceIdentity;
if (isIncognitoOpenClawAgentSqlitePath(target.path, target)) {
const database = cache.databases.get(target.path);
if (!database?.db.isOpen || database.agentId !== target.agentId || !isIncognitoOpenClawAgentDatabase(database)) throw changed();
releaseNative = retainAgentDatabase(database.db);
const claim = createOpenClawAgentDatabaseClaim(database, releaseNative);
releaseNative = claim.release;
sourceIdentity = `incognito:${claim.incarnation}`;
paths = [target.path];
assertSource = () => {
claim.assertCurrent();
if (cache.databases.get(target.path) !== database || !isIncognitoOpenClawAgentDatabase(database)) throw changed();
};
} else {
const candidate = captureSessionStoreReadCandidate(target.path);
const identity = readDatabasePathIdentitySync(candidate.path);
if (!identity.key.startsWith("file:") || identity.canonicalPath !== candidate.physicalPath) throw changed();
sourceIdentity = identity.key;
paths = [.../* @__PURE__ */ new Set([candidate.path, candidate.physicalPath])];
assertSource = () => {
const current = readDatabasePathIdentitySync(candidate.path);
if (current.key !== identity.key || current.canonicalPath !== candidate.physicalPath) throw changed();
};
}
for (const pathname of paths) unregister.push(registerOpenClawAgentDatabaseSyncResource({
agentId: target.agentId,
path: pathname,
revoke: () => {
active = false;
},
close: () => releaseNative()
}));
const assertCurrent = () => {
if (!active) throw changed();
assertSource();
};
assertCurrent();
return await operation(assertCurrent, sourceIdentity);
} finally {
active = false;
for (const release of unregister.toReversed()) release();
releaseNative();
}
}
//#endregion
//#region src/gateway/control-ui-session-pr-snapshot-read.ts
async function loadSessionPullRequests(params, cacheSignal, read) {
read.assertCurrent();
const { loadControlUiSessionPullRequests } = await import("./control-ui-session-prs-Cn3CDg_9.mjs");
return loadControlUiSessionPullRequests(params, {
cacheSignal,
read
});
}
function pushedSnapshot(result) {
return {
...result,
status: result.status ?? (result.rateLimited ? "rate-limited" : "ready")
};
}
const UNAVAILABLE_SNAPSHOT = {
pullRequests: [],
rateLimited: false,
status: "unavailable"
};
/** One-shot reads reuse the subscription owner's lifetime, loader and concurrency. */
function createControlUiSessionPrSnapshotRead(deps) {
return (target, assertCurrent, projection) => {
const assertActive = () => {
if (deps.scope.isClosing) throw new Error("Session pull-request owner is closed");
assertCurrent();
target.assertCurrent?.();
};
assertActive();
return deps.scope.track(() => deps.limit(async () => {
assertActive();
return await deps.withSource(target, async (assertSourceCurrent, sourceIdentity) => {
const assertReadCurrent = () => {
assertActive();
assertSourceCurrent();
};
assertReadCurrent();
try {
const result = await deps.load(target.params, void 0, {
target,
sourceIdentity,
projection,
assertCurrent: assertReadCurrent
});
assertReadCurrent();
const snapshot = pushedSnapshot(result);
if (projection !== "publication") deps.publish?.(target, snapshot);
return snapshot;
} catch {
assertReadCurrent();
if (projection !== "publication") deps.publish?.(target, UNAVAILABLE_SNAPSHOT);
return { ...UNAVAILABLE_SNAPSHOT };
}
});
}));
};
}
//#endregion
//#region src/gateway/control-ui-session-pr-prepared-read.ts
/** Background readers publish into the subscription owner's existing cells and concurrency. */
function createControlUiSessionPrPreparedRead(deps) {
const { scope, limit, withSource, load, keyStates, stateForTarget } = deps;
const preparing = /* @__PURE__ */ new Map();
const publishSnapshot = (state, snapshot) => {
const changed = JSON.stringify(state.snapshot) !== JSON.stringify(snapshot);
state.snapshot = snapshot;
if (changed && state.prepared) sessionChanges.emit({
...state.target.params,
scope: "runtime"
});
};
const read = createControlUiSessionPrSnapshotRead({
scope,
limit,
withSource,
load,
publish: (target, snapshot) => {
const state = keyStates.get(target.params.sessionKey);
if (state?.prepared && state.snapshot === void 0 && state.target.identity === target.identity) publishSnapshot(state, snapshot);
}
});
const readPrepared = (target) => {
if (scope.isClosing) return;
const state = stateForTarget(target.params.sessionKey, target);
const preparedTarget = state.target;
state.prepared = true;
if (target.source === null) state.snapshot ??= {
pullRequests: [],
rateLimited: false,
status: "ready"
};
const getProjection = deps.getSessionRowProjection;
if (!state.snapshot && !preparing.has(state) && getProjection) {
const promise = runInDetachedAsyncContext(() => scope.track(async () => {
const current = await limit(async () => {
if (scope.isClosing || keyStates.get(preparedTarget.params.sessionKey) !== state) return;
return await prepareControlUiSessionPrServiceTarget(getProjection, preparedTarget.params);
});
if (!current) return;
const assertCurrent = () => {
if (scope.isClosing || keyStates.get(preparedTarget.params.sessionKey) !== state || current.identity !== preparedTarget.identity) throw new Error("Prepared session pull-request target changed");
current.assertCurrent?.();
};
assertCurrent();
await read(current, assertCurrent);
})).catch(() => {}).finally(() => preparing.delete(state));
preparing.set(state, promise);
}
return state.snapshot;
};
return {
read,
readPrepared,
publishSnapshot,
settle: () => Promise.allSettled(preparing.values()),
stop: sessionChanges.subscribeFacts((change) => {
if ("sessionKey" in change && (change.facts?.kind === "unchanged" || change.scope === "runtime" && !change.facts && !change.factsInvalidated)) return;
const affected = "sessionKey" in change ? [[change.sessionKey, keyStates.get(change.sessionKey)]] : keyStates;
const invalidated = [];
for (const [key, state] of affected) if (state?.prepared) {
state.snapshot = void 0;
if (state.connIds.size === 0) {
state.cacheLifetime.abort(null);
keyStates.delete(key);
}
invalidated.push(state.target.params);
}
if (!("sessionKey" in change)) sessionChanges.emitBatch(invalidated.map(({ sessionKey, agentId }) => ({
sessionKey,
agentId,
scope: "runtime"
})));
})
};
}
//#endregion
//#region src/gateway/control-ui-session-pr-subscriptions.ts
const CONTROL_UI_SESSION_PR_POLL_INTERVAL_MS = 6e4;
const CONTROL_UI_SESSION_PR_REFRESH_INTERVAL_MS = 1e4;
const CONTROL_UI_SESSION_PR_LOAD_CONCURRENCY = 4;
function parseSessionKeys(value) {
if (!Array.isArray(value) || value.length > 200) return null;
const keys = /* @__PURE__ */ new Set();
for (const entry of value) {
if (typeof entry !== "string") return null;
const key = entry.trim();
if (!key || key.length > 512) return null;
keys.add(key);
}
return [...keys];
}
function parseControlUiSessionPullRequestsSubscribeParams(value) {
if (!value || typeof value !== "object" || !("sessionKeys" in value)) return null;
const raw = value;
const sessionKeys = parseSessionKeys(raw.sessionKeys);
const refreshSessionKeys = raw.refreshSessionKeys === void 0 ? [] : parseSessionKeys(raw.refreshSessionKeys);
if (!sessionKeys || !refreshSessionKeys) return null;
const watched = new Set(sessionKeys);
for (const key of refreshSessionKeys) if (!watched.has(key)) return null;
return {
sessionKeys,
refreshSessionKeys
};
}
/**
* Owns the union of connection replace-sets. Only this union drives GitHub
* refreshes, so hidden/disconnected clients cannot leave orphan polling work.
*/
function createControlUiSessionPullRequestSubscriptions(deps) {
const subscriptions = /* @__PURE__ */ new Map();
const replacements = /* @__PURE__ */ new Set();
const replacementGenerations = /* @__PURE__ */ new Map();
const pendingAdmissions = /* @__PURE__ */ new Map();
const keyStates = /* @__PURE__ */ new Map();
const inflight = /* @__PURE__ */ new Map();
const scheduler = deps.scheduler.scope();
const limit = pLimit(CONTROL_UI_SESSION_PR_LOAD_CONCURRENCY);
const customLoad = deps.load;
const load = customLoad ?? loadSessionPullRequests;
const withSource = (target, operation) => customLoad ? operation(() => {}, target.identity) : withControlUiSessionPrSource(target.readSource, operation);
let pollJob;
const scope = new AsyncWorkScope();
let stopPromise;
const retireKeyStateIfUnused = (sessionKey, state) => {
if (!state || state.prepared || state.connIds.size > 0 || [...pendingAdmissions.get(sessionKey) ?? []].some((isCurrent) => isCurrent())) return;
state.cancelRefresh?.();
state.cacheLifetime.abort(null);
if (keyStates.get(sessionKey) === state) keyStates.delete(sessionKey);
};
const removeMemberships = (connId, previous, retained) => {
for (const key of previous?.keys() ?? []) if (!retained?.has(key)) {
const state = keyStates.get(key);
state?.connIds.delete(connId);
retireKeyStateIfUnused(key, state);
}
};
const stateForTarget = (sessionKey, target, sourceIdentity) => {
const { assertCurrent: _assertCurrent, ...preparedTarget } = target;
const previous = keyStates.get(sessionKey);
if (previous?.target.identity === target.identity && (sourceIdentity === void 0 || previous.sourceIdentity === void 0 || previous.sourceIdentity === sourceIdentity)) {
previous.target = preparedTarget;
previous.sourceIdentity ??= sourceIdentity;
return previous;
}
previous?.cancelRefresh?.();
previous?.cacheLifetime.abort(null);
const state = {
connIds: new Set(previous?.connIds),
target: preparedTarget,
watchLifetime: previous?.watchLifetime ?? {},
prepared: previous?.prepared,
sourceIdentity,
cacheLifetime: new AbortController()
};
keyStates.set(sessionKey, state);
return state;
};
const prepared = createControlUiSessionPrPreparedRead({
scope,
limit,
withSource,
load,
keyStates,
stateForTarget,
getSessionRowProjection: deps.getSessionRowProjection
});
const currentWatcher = async (connId, sessionKey) => {
const watched = subscriptions.get(connId)?.get(sessionKey);
const target = deps.isConnectionActive?.(connId) === false ? void 0 : await watched?.readCurrent();
const subscription = subscriptions.get(connId);
if (scope.isClosing || subscription?.get(sessionKey) !== watched) return;
if (deps.isConnectionActive?.(connId) === false) {
unsubscribe(connId);
return;
}
if (!watched || !target) {
subscription?.delete(sessionKey);
const state = keyStates.get(sessionKey);
state?.connIds.delete(connId);
retireKeyStateIfUnused(sessionKey, state);
if (subscription?.size === 0) {
subscriptions.delete(connId);
if (subscriptions.size === 0) {
pollJob?.cancel();
pollJob = void 0;
}
}
return;
}
if (watched.target.identity !== target.identity) watched.deliveredHash = void 0;
watched.target = target;
stateForTarget(sessionKey, target).connIds.add(connId);
return {
watched,
target
};
};
const currentKeyState = async (sessionKey) => {
for (const connId of keyStates.get(sessionKey)?.connIds ?? []) await currentWatcher(connId, sessionKey);
return keyStates.get(sessionKey);
};
const loadSnapshot = async (sessionKey, isCurrent, refresh) => {
const targetState = await currentKeyState(sessionKey);
if (scope.isClosing || !targetState || !isCurrent()) return UNAVAILABLE_SNAPSHOT;
return withSource(targetState.target, async (assertSourceCurrent, sourceIdentity) => {
const state = stateForTarget(sessionKey, targetState.target, sourceIdentity);
const pending = inflight.get(sessionKey);
if (pending) {
if (pending.state === state && (!refresh || pending.refresh)) {
pending.demands.add(isCurrent);
if (refresh && refresh.watched.refreshPending === refresh.generation) pending.refreshRequests.set(refresh.watched, refresh.generation);
return pending.promise;
}
await pending.promise;
assertSourceCurrent();
return await currentKeyState(sessionKey) === state && isCurrent() ? loadSnapshot(sessionKey, isCurrent, refresh) : UNAVAILABLE_SNAPSHOT;
}
const demands = /* @__PURE__ */ new Set([isCurrent]);
const refreshRequests = /* @__PURE__ */ new Map();
if (refresh && refresh.watched.refreshPending === refresh.generation) refreshRequests.set(refresh.watched, refresh.generation);
const promise = scope.track(async () => {
const delay = refresh ? (state.refreshedAt ?? -Infinity) + CONTROL_UI_SESSION_PR_REFRESH_INTERVAL_MS - scheduler.now() : 0;
if (delay > 0) {
await new Promise((resolve) => {
const refreshJob = scheduler.schedule({
id: `control-ui-session-pr-refresh:${sessionKey}`,
delayMs: delay,
run: resolve
});
state.cancelRefresh = () => {
refreshJob.cancel();
resolve();
};
});
state.cancelRefresh = void 0;
}
return await limit(async () => {
if (!Array.from(demands).some((current) => current()) || await currentKeyState(sessionKey) !== state) return UNAVAILABLE_SNAPSHOT;
if (refresh) state.refreshedAt = scheduler.now();
const snapshot = await load({
...state.target.params,
...refresh ? { refresh: true } : {}
}, state.cacheLifetime.signal, {
target: state.target,
sourceIdentity,
assertCurrent: () => {
assertSourceCurrent();
if (scope.isClosing || !Array.from(demands).some((current) => current()) || keyStates.get(sessionKey) !== state) throw new Error("Session pull-request watchers changed");
for (const connId of state.connIds) {
const watched = subscriptions.get(connId)?.get(sessionKey);
if (!watched || watched.target.identity !== state.target.identity || deps.isConnectionActive?.(connId) === false) continue;
try {
watched.target.assertCurrent?.();
return;
} catch {}
}
throw new Error("Session pull-request watchers changed");
}
}).then(pushedSnapshot).catch(() => ({ ...UNAVAILABLE_SNAPSHOT }));
if (await currentKeyState(sessionKey) === state) {
assertSourceCurrent();
prepared.publishSnapshot(state, snapshot);
await push(new Set(state.connIds), sessionKey, state, snapshot, assertSourceCurrent, refreshRequests);
}
return snapshot;
});
}).catch(() => UNAVAILABLE_SNAPSHOT).finally(() => {
if (inflight.get(sessionKey)?.promise === promise) inflight.delete(sessionKey);
});
inflight.set(sessionKey, {
promise,
refresh: Boolean(refresh),
refreshRequests,
state,
demands
});
return promise;
}).catch(() => UNAVAILABLE_SNAPSHOT);
};
const push = (connIds, sessionKey, state, snapshot, assertSourceCurrent, refreshRequests) => {
if (connIds.size === 0) return Promise.resolve();
const hash = JSON.stringify(snapshot);
const isDelivered = (watched) => {
const request = refreshRequests?.get(watched);
return (!request || watched.refreshPending !== request) && watched.deliveredHash === hash;
};
const stateDelivery = (state.delivery ?? Promise.resolve()).then(async () => {
const sessions = Object.create(null);
sessions[sessionKey] = snapshot;
for (const connId of connIds) {
const watched = subscriptions.get(connId)?.get(sessionKey);
if (!watched || isDelivered(watched)) continue;
const delivery = (watched.delivery ?? Promise.resolve()).then(async () => {
const current = await currentWatcher(connId, sessionKey);
if (!current || scope.isClosing || current.watched !== watched || subscriptions.get(connId)?.get(sessionKey) !== watched || deps.isConnectionActive?.(connId) === false || keyStates.get(sessionKey) !== state || isDelivered(watched)) return;
assertSourceCurrent();
try {
current.target.assertCurrent?.();
} catch {
return;
}
const refreshRequest = refreshRequests?.get(watched);
deps.broadcastToConnIds(CONTROL_UI_SESSION_PULL_REQUESTS_CHANGED_EVENT, { sessions }, /* @__PURE__ */ new Set([connId]), {
sessionKeys: [state.target.params.sessionKey],
agentId: state.target.params.agentId
});
if (subscriptions.get(connId)?.get(sessionKey) === watched) {
watched.deliveredHash = hash;
if (refreshRequest && watched.refreshPending === refreshRequest) delete watched.refreshPending;
}
}).finally(() => {
if (watched.delivery === delivery) delete watched.delivery;
});
watched.delivery = delivery;
await delivery;
}
}).finally(() => {
if (state.delivery === stateDelivery) delete state.delivery;
});
state.delivery = stateDelivery;
return stateDelivery;
};
const schedulePoll = () => {
if (scope.isClosing || pollJob || subscriptions.size === 0) return;
pollJob = scheduler.schedule({
id: "control-ui-session-pr-poll",
atMs: scheduler.now() + CONTROL_UI_SESSION_PR_POLL_INTERVAL_MS,
everyMs: CONTROL_UI_SESSION_PR_POLL_INTERVAL_MS,
run: pollNow
});
};
const pollNow = () => {
if (scope.isClosing) return Promise.resolve();
return scope.track(async () => {
const loads = [];
for (const [sessionKey, state] of keyStates) if (state.connIds.size > 0) {
const watchLifetime = state.watchLifetime;
loads.push(loadSnapshot(sessionKey, () => keyStates.get(sessionKey)?.watchLifetime === watchLifetime));
}
await Promise.all([
Promise.allSettled(replacements),
prepared.settle(),
...loads
]);
});
};
const replace = (connId, sessionKeys, refreshSessionKeys = /* @__PURE__ */ new Set(), onAdmitted) => {
let admitted = false;
const admit = () => {
if (!admitted) {
admitted = true;
onAdmitted?.();
}
};
if (scope.isClosing) {
admit();
return Promise.resolve();
}
const normalizedConnId = connId.trim();
if (!normalizedConnId || deps.isConnectionActive?.(normalizedConnId) === false) {
unsubscribe(normalizedConnId);
admit();
return Promise.resolve();
}
const previousGeneration = replacementGenerations.get(normalizedConnId);
const retainedKeys = new Set(sessionKeys.filter((key) => keyStates.has(key)));
const generation = { retainedKeys };
replacementGenerations.set(normalizedConnId, generation);
for (const key of sessionKeys) {
const watched = subscriptions.get(normalizedConnId)?.get(key);
if (watched && refreshSessionKeys.has(key)) watched.refreshPending = generation;
}
const isCurrentReplacement = () => !scope.isClosing && replacementGenerations.get(normalizedConnId) === generation;
const replacement = scope.track(async () => {
for (const key of retainedKeys) {
const admissions = pendingAdmissions.get(key) ?? /* @__PURE__ */ new Set();
admissions.add(isCurrentReplacement);
pendingAdmissions.set(key, admissions);
}
try {
for (const key of previousGeneration?.retainedKeys ?? []) retireKeyStateIfUnused(key, keyStates.get(key));
const previousSubscription = subscriptions.get(normalizedConnId);
const subscription = /* @__PURE__ */ new Map();
for (const key of sessionKeys) {
const parsed = parseAgentSessionKey(key);
const session = parsed?.rest === "global" ? {
sessionKey: "global",
agentId: parsed.agentId
} : { sessionKey: key };
const previous = previousSubscription?.get(key);
const readCurrent = previous && await previous.readCurrent() ? previous.readCurrent : await deps.prepareRead(normalizedConnId, session);
const target = await readCurrent?.();
if (!readCurrent || !target) continue;
const next = previous?.target.identity === target.identity && readCurrent === previous.readCurrent ? previous : {
readCurrent,
target
};
if (next !== previous && refreshSessionKeys.has(key)) next.refreshPending = generation;
subscription.set(key, next);
}
const currentReplacement = isCurrentReplacement();
if (!currentReplacement) {
const current = subscriptions.get(normalizedConnId);
for (const [key, watched] of subscription) if (!refreshSessionKeys.has(key) || current?.get(key) !== watched || watched.refreshPending !== generation) subscription.delete(key);
}
if (subscription.size === 0) {
if (currentReplacement) unsubscribe(normalizedConnId);
admit();
return;
}
if (currentReplacement) {
subscriptions.set(normalizedConnId, subscription);
removeMemberships(normalizedConnId, previousSubscription, subscription);
for (const [key, watched] of subscription) stateForTarget(key, watched.target).connIds.add(normalizedConnId);
schedulePoll();
}
admit();
await Promise.all(Array.from(subscription, async ([sessionKey, watched]) => {
const targetState = await currentKeyState(sessionKey);
if (!targetState) return;
return withSource(targetState.target, async (assertSourceCurrent, sourceIdentity) => {
const state = stateForTarget(sessionKey, targetState.target, sourceIdentity);
const isCurrent = () => subscriptions.get(normalizedConnId)?.get(sessionKey) === watched;
const refresh = refreshSessionKeys.has(sessionKey);
const cached = refresh ? void 0 : state.snapshot;
if (cached) {
if (!watched.deliveredHash) await push(/* @__PURE__ */ new Set([normalizedConnId]), sessionKey, state, cached, assertSourceCurrent);
return;
}
const snapshot = await loadSnapshot(sessionKey, isCurrent, refresh ? {
watched,
generation
} : void 0);
assertSourceCurrent();
if (isCurrent() && (refresh ? watched.refreshPending === generation : !watched.deliveredHash)) await push(/* @__PURE__ */ new Set([normalizedConnId]), sessionKey, state, snapshot, assertSourceCurrent, refresh ? /* @__PURE__ */ new Map([[watched, generation]]) : void 0);
}).catch(() => {});
}));
} finally {
admit();
for (const key of retainedKeys) {
const admissions = pendingAdmissions.get(key);
admissions?.delete(isCurrentReplacement);
if (admissions?.size === 0) pendingAdmissions.delete(key);
retireKeyStateIfUnused(key, keyStates.get(key));
}
}
});
replacements.add(replacement);
const releaseReplacement = () => {
replacements.delete(replacement);
if (replacementGenerations.get(normalizedConnId) === generation) replacementGenerations.delete(normalizedConnId);
};
replacement.then(releaseReplacement, releaseReplacement);
return replacement;
};
const unsubscribe = (connId) => {
const normalizedConnId = connId.trim();
if (!normalizedConnId) return;
const generation = replacementGenerations.get(normalizedConnId);
replacementGenerations.delete(normalizedConnId);
removeMemberships(normalizedConnId, subscriptions.get(normalizedConnId));
subscriptions.delete(normalizedConnId);
for (const key of generation?.retainedKeys ?? []) retireKeyStateIfUnused(key, keyStates.get(key));
if (subscriptions.size === 0) {
pollJob?.cancel();
pollJob = void 0;
}
};
const stop = () => {
if (stopPromise) return stopPromise;
scope.beginClose();
scheduler.beginClose();
prepared.stop();
subscriptions.clear();
replacementGenerations.clear();
replacements.clear();
for (const state of keyStates.values()) {
state.cancelRefresh?.();
state.cacheLifetime.abort(null);
}
keyStates.clear();
stopPromise = Promise.all([scope.drain(), scheduler.stop()]).then(() => {
inflight.clear();
});
return stopPromise;
};
return {
read: prepared.read,
readPrepared: prepared.readPrepared,
replace,
unsubscribe,
pollNow,
stop
};
}
//#endregion
export { parseControlUiSessionPullRequestsSubscribeParams as n, withControlUiSessionPrSource as r, createControlUiSessionPullRequestSubscriptions as t };