mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
d6f70a96cb
* fix(plugins): preserve selected command identity * test(telegram): use scoped command registries * test(telegram): isolate command runtime fixtures * test(telegram): warm native command runtime * refactor(plugins): keep command metadata private * fix(plugins): accept synchronous command handlers * fix(plugins): scope command drain bypass to live execution * test(telegram): use scoped command registry fixtures * test(telegram): isolate native menu runtime fixtures * test(telegram): isolate login session store * test(telegram): surface login flow failures * test(telegram): preload native login module * test(telegram): scope native command registries * fix(plugins): complete command dispatch contracts * fix(plugins): break command dispatch import cycles * fix(plugins): stabilize command dispatch contracts * fix(channels): keep plugin dispatch options internal * fix(plugins): keep command dispatch carrier opaque * test(channels): align delivery adapter fixtures * test(delivery): align custody ownership coverage * test(delivery): align latest queue reconciliation * test(channels): drop obsolete delivery wrappers * fix(plugins): rebind channel reload starts * fix(plugins): scope command catalog reloads * fix(ci): align current runtime contracts * chore(plugin-sdk): refresh API baseline
91 lines
2.7 KiB
TypeScript
91 lines
2.7 KiB
TypeScript
/** Per-registry command execution admission and retirement drain. */
|
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
import { isPluginRegistryRetired } from "./registry-lifecycle.js";
|
|
import type { PluginRegistry } from "./registry-types.js";
|
|
|
|
type PluginCommandExecutionState = {
|
|
count: number;
|
|
waiters: Array<() => void>;
|
|
};
|
|
|
|
type PluginCommandExecutionToken = {
|
|
registry: PluginRegistry;
|
|
active: boolean;
|
|
};
|
|
|
|
const executionStates = new WeakMap<PluginRegistry, PluginCommandExecutionState>();
|
|
const executionContext = new AsyncLocalStorage<ReadonlySet<PluginCommandExecutionToken>>();
|
|
|
|
function getExecutionState(registry: PluginRegistry): PluginCommandExecutionState {
|
|
const existing = executionStates.get(registry);
|
|
if (existing) {
|
|
return existing;
|
|
}
|
|
const created = { count: 0, waiters: [] };
|
|
executionStates.set(registry, created);
|
|
return created;
|
|
}
|
|
|
|
export function getPluginCommandExecutionCount(registry: PluginRegistry): number {
|
|
return executionStates.get(registry)?.count ?? 0;
|
|
}
|
|
|
|
function beginPluginCommandExecution(registry: PluginRegistry): boolean {
|
|
if (isPluginRegistryRetired(registry)) {
|
|
return false;
|
|
}
|
|
getExecutionState(registry).count += 1;
|
|
return true;
|
|
}
|
|
|
|
function endPluginCommandExecution(registry: PluginRegistry): void {
|
|
const state = getExecutionState(registry);
|
|
if (state.count <= 0) {
|
|
throw new Error("Plugin command execution lock is unbalanced.");
|
|
}
|
|
state.count -= 1;
|
|
if (state.count !== 0) {
|
|
return;
|
|
}
|
|
const waiters = state.waiters.splice(0);
|
|
for (const resolve of waiters) {
|
|
resolve();
|
|
}
|
|
}
|
|
|
|
export function isPluginCommandExecutionActiveHere(registry: PluginRegistry): boolean {
|
|
return [...(executionContext.getStore() ?? [])].some(
|
|
(token) => token.registry === registry && token.active,
|
|
);
|
|
}
|
|
|
|
export async function withPluginCommandExecution<T>(
|
|
registry: PluginRegistry,
|
|
run: () => T | Promise<T>,
|
|
): Promise<{ admitted: true; value: T } | { admitted: false }> {
|
|
if (!beginPluginCommandExecution(registry)) {
|
|
return { admitted: false };
|
|
}
|
|
const token: PluginCommandExecutionToken = { registry, active: true };
|
|
const active = new Set(
|
|
[...(executionContext.getStore() ?? [])].filter((inherited) => inherited.registry !== registry),
|
|
);
|
|
active.add(token);
|
|
try {
|
|
return { admitted: true, value: await executionContext.run(active, run) };
|
|
} finally {
|
|
token.active = false;
|
|
endPluginCommandExecution(registry);
|
|
}
|
|
}
|
|
|
|
export async function waitForPluginCommandExecutions(registry: PluginRegistry): Promise<void> {
|
|
const state = getExecutionState(registry);
|
|
if (state.count === 0) {
|
|
return;
|
|
}
|
|
await new Promise<void>((resolve) => {
|
|
state.waiters.push(resolve);
|
|
});
|
|
}
|