openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
355 lines (354 loc) • 13.9 kB
JavaScript
import { t as createDeferredCore } from "./deferred-D0La5CRk.mjs";
import { a as resolveGlobalSingleton } from "./global-singleton-0m-JziJW.mjs";
import { c as trackAsyncWork, t as AsyncWorkScope } from "./async-work-scope-BYeX3gh6.mjs";
import { i as pluginInstanceInvocation } from "./plugin-instance-invocation-BDEVkXSF.mjs";
import { t as PluginRuntimeCloseRetainedError } from "./runtime-close-error-BK5xOGjk.mjs";
import { a as getPluginValueInstance, c as pluginInvocationContext, t as getPluginInstance } from "./plugin-instance-scope-C4zdRFkI.mjs";
import { S as markPluginRegistriesRetired } from "./registry-lifecycle-ywOMiXNf.mjs";
//#region src/plugins/plugin-invocation-scope.ts
/** Finite execution custody for one host-selected registry and its exact instances. */
var PluginInvocationScope = class PluginInvocationScope {
constructor(registry, instances, options = {}) {
this.registry = registry;
this.bindings = /* @__PURE__ */ new Map();
this.consumers = /* @__PURE__ */ new Map();
this.closed = false;
this.consumerKind = options.kind ?? "work";
try {
for (const instance of new Set(instances)) if (options.retained) {
const acquire = () => instance.retainConsumer((run) => this.run(run), registry, this.consumerKind);
const parent = options.parent?.consumer(instance);
const consumer = parent ? parent.run(acquire) : acquire();
this.consumers.set(instance, consumer);
this.bindings.set(instance, consumer);
} else this.bindings.set(instance, {
run: (run) => this.run(() => instance.runInRegistry(registry, run)),
wrap: instance.createRegistryView(registry, (run) => this.run(run))
});
} catch (error) {
this.release();
throw error;
}
}
consumer(instance) {
this.assertOpen();
return this.consumers.get(instance);
}
assertOpen() {
if (this.closed) throw new Error("Plugin invocation scope is closed");
}
lookup(instance) {
const binding = this.bindings.get(instance);
if (binding) this.assertOpen();
return binding;
}
run(run) {
this.assertOpen();
return pluginInvocationContext.run(this, run);
}
wrap(value) {
if (!value || typeof value !== "object" && typeof value !== "function") return value;
const instance = getPluginValueInstance(value);
return instance ? this.lookup(instance)?.wrap(value) ?? value : value;
}
/** Transfer custody before revoking callbacks captured by ordinary engine operations. */
beginCleanup() {
this.assertOpen();
const cleanup = new PluginInvocationScope(this.registry, this.bindings.keys());
const finished = createDeferredCore();
const closed = Promise.all([...this.consumers].map(([instance, consumer]) => consumer.close(() => {
const teardown = pluginInstanceInvocation.getStore();
if (!teardown) throw new Error("Plugin consumer cleanup has no invocation");
const run = (operation) => cleanup.run(() => pluginInstanceInvocation.run(teardown, () => instance.runCleanup(operation)));
cleanup.bindings.set(instance, {
run,
wrap: instance.createRegistryView(this.registry, run)
});
return finished.promise;
})));
closed.catch(() => {});
this.closed = true;
return {
scope: cleanup,
release: async () => {
cleanup.release();
finished.resolve();
await closed;
}
};
}
release() {
if (this.closed) return;
this.closed = true;
for (const consumer of this.consumers.values()) consumer.release();
}
};
/** Enumerate exact instances already represented by this finite registry view. */
function collectRegistryInvocationInstances(registry) {
const instances = /* @__PURE__ */ new Set();
for (const record of registry.plugins) {
const instance = getPluginInstance(record);
if (instance) instances.add(instance);
}
for (const { host } of registry.decisionProviders) {
const instance = getPluginInstance(host.record);
if (instance) instances.add(instance);
}
for (const entry of registry.channels) {
const instance = entry.borrowedRuntimeRecord && getPluginInstance(entry.borrowedRuntimeRecord);
if (instance) instances.add(instance);
}
const values = [
...registry.tools.map(({ factory }) => factory),
...registry.channels.map(({ plugin }) => plugin),
...[...registry.contextEngines.values()].map(({ factory }) => factory),
...registry.widgetPresenters.map(({ presenter }) => presenter),
...registry.memoryCorpusSupplements.map(({ supplement }) => supplement),
...registry.memoryPromptPreparations.map(({ prepare }) => prepare),
...registry.memoryPromptSupplements.map(({ builder }) => builder)
];
for (const value of values) {
const instance = getPluginValueInstance(value);
if (instance) instances.add(instance);
}
return instances;
}
//#endregion
//#region src/plugins/registry-registration-resources.ts
/** Physical registration custody, independent of registry execution authority. */
var PluginRegistrationResourceSource = class {
#registrations = /* @__PURE__ */ new Map();
#pending = /* @__PURE__ */ new Set();
#dependencies = [];
#cleanupWork = new AsyncWorkScope();
#claims = 0;
#closed = false;
constructor(retire) {
this.retire = retire;
}
acquireClaim(owner) {
if (this.#closed) throw new Error("Plugin registration resources have been released");
this.#claims++;
let release;
return { release: () => {
if (!release) {
const last = --this.#claims === 0;
this.#closed = last;
const cleanup = this.#cleanupWork.track(async () => {
await Promise.resolve();
const disposals = [...this.#registrations].filter(([, entry]) => entry.rolledBack ? owner === "inspection" : last).map(([pluginId, entry]) => this.#dispose(pluginId, entry));
await this.#waitForRegistrations();
const failures = (await Promise.allSettled(disposals)).flatMap((outcome) => outcome.status === "fulfilled" ? outcome.value : [new PluginRuntimeCloseRetainedError(outcome.reason)]);
if (last) {
await Promise.allSettled([...this.#registrations].map(([pluginId, entry]) => this.#dispose(pluginId, entry)));
try {
await this.retire();
} catch (error) {
failures.push(error instanceof Error ? error : new Error("Plugin inspection cleanup failed", { cause: error }));
} finally {
await Promise.all([...this.#registrations.values()].map(({ work }) => work.drain()));
}
for (const releaseDependency of this.#dependencies.splice(0)) try {
await releaseDependency();
} catch (cause) {
failures.push(new Error("Borrowed plugin registration resources could not be disposed", { cause }));
}
}
return failures;
});
release = (async () => {
try {
return await cleanup;
} finally {
if (last) await this.#cleanupWork.run(() => this.#cleanupWork.drain());
}
})();
}
return release;
} };
}
/** Acquires custody before publication and relinquishes it after final physical disposal. */
retainDependency(acquire) {
if (this.#closed) throw new Error("Plugin registration resources have been released");
this.#dependencies.push(acquire().release);
}
#registration(pluginId) {
let entry = this.#registrations.get(pluginId);
if (!entry) {
entry = {
disposers: [],
work: new AsyncWorkScope()
};
this.#registrations.set(pluginId, entry);
}
return entry;
}
runRegistration(pluginId, run, runCleanup) {
const entry = this.#registration(pluginId);
entry.runCleanup ??= runCleanup;
try {
entry.work.run(run);
} finally {
this.#trackRegistration(entry.work.runWhenIdle(() => void 0));
}
}
register(pluginId, disposer) {
this.#registration(pluginId).disposers.push(disposer);
}
trackRegistration(pending) {
this.#trackRegistration(trackAsyncWork(() => pending));
}
#trackRegistration(pending) {
const completion = pending.then(() => void 0, () => void 0);
this.#pending.add(completion);
completion.then(() => this.#pending.delete(completion));
}
rollback(pluginId, retire) {
const entry = this.#registration(pluginId);
entry.rolledBack = true;
entry.retire ??= retire;
this.#dispose(pluginId, entry);
}
async #waitForRegistrations() {
while (this.#pending.size > 0) await Promise.all(this.#pending);
}
#pendingWork() {
return [...this.#registrations.values()].filter((entry) => !entry.disposalStarted).map((entry) => entry.work);
}
#dispose(pluginId, entry) {
return entry.disposal ??= Promise.resolve().then(async () => {
const signalCleanup = async () => {
entry.work.beginClose();
await this.#waitForRegistrations();
await AsyncWorkScope.runWhenAllIdle(() => this.#pendingWork(), () => void 0);
};
await (entry.runCleanup ? entry.runCleanup(signalCleanup) : signalCleanup());
const failures = [];
try {
await AsyncWorkScope.runWhenAllIdle(() => this.#pendingWork(), () => entry.work.track(async () => {
entry.disposalStarted = true;
const disposers = entry.disposers.splice(0);
for (const { id, dispose } of disposers) try {
await dispose();
} catch (cause) {
failures.push(new Error(`Plugin inspection disposal failed: ${pluginId}:${id}`, { cause }));
}
}));
try {
await entry.work.runWhenIdle(() => entry.retire?.());
} catch (cause) {
failures.push(new Error(`Plugin inspection retirement failed: ${pluginId}`, { cause }));
}
return failures;
} finally {
if (entry.rolledBack) await entry.work.drain();
else await entry.work.runWhenIdle(() => void 0);
}
});
}
};
//#endregion
//#region src/plugins/registry-inspection-resources.ts
const inspections = resolveGlobalSingleton(Symbol.for("openclaw.pluginRegistryInspectionResources"), () => /* @__PURE__ */ new WeakMap());
function getPluginRegistryInspectionResources(registry) {
return inspections.get(registry);
}
function throwDisposalFailures(failures) {
if (failures.length > 0) throw new AggregateError(failures, "Plugin inspection resources could not all be disposed");
}
/** Owns only an explicitly acquired, uncached inspection's registration resources. */
var PluginRegistryInspectionResources = class {
#rollbackInstances = /* @__PURE__ */ new Set();
#source = new PluginRegistrationResourceSource(() => this.retire(this.#registry, this.#rollbackInstances));
#claim = this.#source.acquireClaim("inspection");
#registries = /* @__PURE__ */ new Set();
#dependencies = /* @__PURE__ */ new WeakSet();
#registry;
#adoptedInvocations;
#release;
constructor(retire) {
this.retire = retire;
}
attach(registry) {
if (this.#release) throw new Error("Plugin inspection resources have been released");
this.#registry ??= registry;
this.#registries.add(registry);
inspections.set(registry, this);
}
register(pluginId, disposer) {
this.#source.register(pluginId, disposer);
}
runRegistration(pluginId, run, runCleanup) {
this.#source.runRegistration(pluginId, run, runCleanup);
}
trackRegistration(pending) {
this.#source.trackRegistration(pending);
}
rollback(pluginId, retire, instance) {
if (instance) this.#rollbackInstances.add(instance);
this.#source.rollback(pluginId, retire);
}
/** Copied callbacks keep their source through this inspection's final disposer. */
retainDependency(dependency) {
if (this.#release) throw new Error("Plugin inspection resources have been released");
if (dependency !== this) {
this.#source.retainDependency(() => dependency.retain());
this.#dependencies.add(dependency);
}
}
/** Adopt executable donor custody before the acquired view can escape to a caller. */
adoptInvocations(registry, donor) {
if (this.#release || this.#adoptedInvocations) throw new Error("Plugin inspection invocation adoption is closed");
const primaryInstances = new Set(this.#registry?.plugins.flatMap((record) => {
const instance = getPluginInstance(record);
return instance ? [instance] : [];
}) ?? []);
const borrowed = [...collectRegistryInvocationInstances(registry)].filter((instance) => !primaryInstances.has(instance));
const donorSource = donor && getPluginRegistryInspectionResources(donor);
let physical;
this.#source.retainDependency(() => ({ release: async () => {
this.#adoptedInvocations?.release();
await physical?.release();
} }));
if (donorSource && donorSource !== this) {
physical = donorSource.retain();
this.#dependencies.add(donorSource);
}
this.#adoptedInvocations = new PluginInvocationScope(registry, borrowed, {
retained: true,
kind: "custody"
});
}
wrapAdoptedValue(value) {
return this.#adoptedInvocations ? this.#adoptedInvocations.wrap(value) : value;
}
/** Logical use owns its execution consumers separately from this inspection's physical claims. */
createInvocationScope(registry, instances = collectRegistryInvocationInstances(registry)) {
if (this.#release) throw new Error("Plugin inspection resources have been released");
return new PluginInvocationScope(registry, instances, {
retained: true,
parent: this.#adoptedInvocations
});
}
/** Recorded coverage survives retirement; retain() still checks this inspection's lifetime. */
coversSource(source) {
return source === this || this.#dependencies.has(source);
}
/** Retains physical resources without extending this inspection's authority. */
retain() {
if (this.#release) throw new Error("Plugin inspection resources have been released");
const claim = this.#source.acquireClaim("borrower");
let release;
return { release: () => release ??= claim.release().then(throwDisposalFailures) };
}
release() {
if (!this.#release) {
this.#release = this.#claim.release().then(throwDisposalFailures);
markPluginRegistriesRetired(this.#registries);
this.#registries.clear();
}
return this.#release;
}
};
//#endregion
export { collectRegistryInvocationInstances as i, getPluginRegistryInspectionResources as n, PluginInvocationScope as r, PluginRegistryInspectionResources as t };