openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
141 lines (140 loc) • 6.82 kB
JavaScript
import { c as normalizeOptionalLowercaseString } from "./string-coerce-CIXf7egm.js";
import { r as truncateUtf16Safe } from "./utf16-slice-D_ngcYKd.js";
import { g as redactToolPayloadTextWithConfig } from "./redact-BtvPPfTi.js";
import { n as resolveManifestCommandAliasOwnerInRegistry } from "./manifest-command-aliases-DKcke8h_.js";
import { c as isPluginRegistryRetired } from "./registry-lifecycle-BozndFXl.js";
import { n as resolveSelectedPluginCommandRegistry, t as listRegisteredPluginCommands } from "./plugin-command-registry-BOeSDUSE.js";
import { n as projectPluginCommandNativeMetadata, t as pluginCommandSupportsChannel } from "./plugin-command-metadata-jSFxBwiS.js";
import { n as parsePluginInvocation, t as matchRegisteredPluginCommand } from "./plugin-command-matcher-DvAKG79h.js";
//#region src/plugins/plugin-command-dispatch-contract.ts
/** Lightweight reply-option contract for prepared plugin command ownership. */
const PLUGIN_COMMAND_DISPATCH = Symbol.for("openclaw.pluginCommandDispatch");
//#endregion
//#region src/plugins/plugin-command-runtime.ts
/** Registry-bound plugin command selection and execution for native/channel surfaces. */
const dispatchSelections = /* @__PURE__ */ new WeakMap();
const runtimeStates = /* @__PURE__ */ new WeakMap();
const INVALID_SELECTION_REPLY = { text: "⚠️ This command selection is no longer valid. Please try again." };
const RETIRED_SELECTION_REPLY = { text: "⚠️ This command is no longer available after the plugin registry changed. Please try again." };
function createSelectedPluginCommandDispatch(runtime, state, selection, channel) {
const dispatch = Object.freeze({
kind: "plugin",
async execute(context) {
if (this !== dispatch) return { ...INVALID_SELECTION_REPLY };
return await executeSelectedPluginCommand(runtime, dispatch, context);
}
});
dispatchSelections.set(dispatch, {
runtime,
registry: state.registry,
selection,
channel: normalizeOptionalLowercaseString(channel) ?? ""
});
return dispatch;
}
async function executeSelectedPluginCommand(runtime, dispatch, context) {
const selected = dispatchSelections.get(dispatch);
if (!selected || runtime && selected.runtime !== runtime) return { ...INVALID_SELECTION_REPLY };
if (isPluginRegistryRetired(selected.registry)) return { ...RETIRED_SELECTION_REPLY };
const channel = normalizeOptionalLowercaseString(context.channel) ?? "";
if (selected.channel !== channel) return { ...INVALID_SELECTION_REPLY };
const { selection } = selected;
if (selection.availability === "manifest-only") {
if (!context.isAuthorizedSender) return { text: "⚠️ This command requires authorization." };
const reason = truncateUtf16Safe(redactToolPayloadTextWithConfig(selection.plugin.error?.split(/[\r\n]/, 1)[0]?.trim() || "reason not recorded", context.config.logging), 240);
return { text: `⚠️ Plugin "${selection.plugin.id}" failed to load: ${reason}. Run \`openclaw doctor\` and check the gateway logs.` };
}
const { executeRegisteredPluginCommand } = await import("./plugin-command-execution-DGTNE-Zd.js");
if (isPluginRegistryRetired(selected.registry)) return { ...RETIRED_SELECTION_REPLY };
return await executeRegisteredPluginCommand(selected.registry, {
...context,
args: selection.args,
command: selection.command
});
}
/** Validates and executes a dispatch carried through the core reply pipeline. */
async function executePluginCommandDispatch(dispatch, context) {
return await executeSelectedPluginCommand(void 0, dispatch, context);
}
/** Creates one command runtime bound permanently to the current scoped registry generation. */
function createPluginCommandRuntime() {
const registry = resolveSelectedPluginCommandRegistry();
if (!registry) throw new Error("Plugin command runtime requires an active or request-scoped registry.");
const state = Object.freeze({
registry,
commands: Object.freeze(listRegisteredPluginCommands(registry))
});
const assertCurrent = () => {
if (isPluginRegistryRetired(state.registry)) throw new Error("Plugin command runtime is bound to a retired registry generation.");
};
const runtime = Object.freeze({
listNativeCandidates(provider) {
assertCurrent();
const channel = normalizeOptionalLowercaseString(provider) ?? "";
return Object.freeze(state.commands.filter((command) => pluginCommandSupportsChannel(command, channel)).map((command) => {
const metadata = projectPluginCommandNativeMetadata(command, channel);
return Object.freeze({
...metadata,
prepareDispatch(rawArgs) {
const args = rawArgs?.trim();
if (args && !command.acceptsArgs) return Object.freeze({ kind: "non-plugin" });
return createSelectedPluginCommandDispatch(runtime, state, {
availability: "loaded",
command,
args
}, channel);
}
});
}));
},
retainNativeCatalog: assertCurrent
});
runtimeStates.set(runtime, state);
return runtime;
}
/** Core-only text matcher that returns a dispatch from the same bound runtime. */
function matchPluginCommandInvocation(runtime, commandBody, options) {
const state = runtimeStates.get(runtime);
if (!state) return null;
if (isPluginRegistryRetired(state.registry)) throw new Error("Plugin command runtime is bound to a retired registry generation.");
const channel = normalizeOptionalLowercaseString(options.channel) ?? "";
const provider = normalizeOptionalLowercaseString(options.provider) ?? channel;
const match = matchRegisteredPluginCommand({
commands: state.commands,
commandBody,
channel,
aliasScope: {
kind: "provider",
provider
}
});
if (!match) {
const owner = parsePluginInvocation(commandBody)?.keys.map((key) => resolveManifestCommandAliasOwnerInRegistry({
command: key.slice(1),
registry: state.registry
})).find((candidate) => candidate !== void 0);
const plugin = owner?.kind === "runtime-slash" ? state.registry.plugins.find((entry) => entry.id === owner.pluginId) : void 0;
if (plugin?.status !== "error" || !plugin.enabled) return null;
return Object.freeze({
dispatch: createSelectedPluginCommandDispatch(runtime, state, {
availability: "manifest-only",
plugin
}, channel),
acceptsArgs: true,
requireAuth: true
});
}
const metadata = projectPluginCommandNativeMetadata(match.command, provider);
return Object.freeze({
dispatch: createSelectedPluginCommandDispatch(runtime, state, {
availability: "loaded",
command: match.command,
args: match.args
}, channel),
acceptsArgs: metadata.acceptsArgs,
requireAuth: metadata.requireAuth,
...metadata.progressMessage ? { progressMessage: metadata.progressMessage } : {}
});
}
//#endregion
export { PLUGIN_COMMAND_DISPATCH as i, executePluginCommandDispatch as n, matchPluginCommandInvocation as r, createPluginCommandRuntime as t };