fix(cli): narrow message plugin registry loads

This commit is contained in:
Peter Steinberger
2026-04-27 20:55:47 +01:00
parent 161b722303
commit 72f3c840c7
4 changed files with 55 additions and 3 deletions

View File

@@ -16,6 +16,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Channels/Telegram: skip the optional webhook-info API call during polling-mode status checks and startup bot-label probes so long-polling setups avoid an unnecessary Telegram round trip. Carries forward #72990. Thanks @danielgruneberg.
- CLI/message: load only the selected channel plugin for targeted `openclaw message` actions, and fall back to configured channel plugins when the channel must be inferred, so scripted sends avoid full bundled plugin registry scans. Fixes #73006. Thanks @jasonftl.
- CLI/models: keep route-first `models status --json` stdout reserved for the JSON payload by routing auth-profile and startup diagnostics to stderr. Fixes #72962. Thanks @vishutdhar.
- Sessions: ignore future-dated session activity timestamps during reset freshness checks and cap future `updatedAt` values at the merge boundary so clock-skewed messages cannot keep stale sessions alive forever. Fixes #72989. Thanks @martingarramon.
- Plugins/CLI: allow managed plugin installs when the active extensions root is a symlink to a real state directory, while keeping nested target symlinks blocked and suppressing misleading hook-pack fallback errors for install-boundary failures. Fixes #72946. Thanks @mayank6136.

View File

@@ -22,6 +22,7 @@ Channel selection:
- `--channel` required if more than one channel is configured.
- If exactly one channel is configured, it becomes the default.
- Values: `discord|googlechat|imessage|matrix|mattermost|msteams|signal|slack|telegram|whatsapp` (Mattermost requires plugin)
- `openclaw message` loads only the selected channel plugin when `--channel` or a channel-prefixed target is present; otherwise it loads configured channel plugins for default-channel inference.
Target formats (`--target`):

View File

@@ -96,11 +96,46 @@ describe("runMessageAction", () => {
it("calls exit(0) after successful message delivery", async () => {
await runSendAction();
expect(ensurePluginRegistryLoaded).toHaveBeenCalledOnce();
expect(ensurePluginRegistryLoaded).toHaveBeenCalledWith({
scope: "configured-channels",
onlyPluginIds: ["discord"],
});
expect(exitMock).toHaveBeenCalledOnce();
expect(exitMock).toHaveBeenCalledWith(0);
});
it("loads configured channel plugins when no target channel is known yet", async () => {
await runSendAction({ channel: undefined });
expect(ensurePluginRegistryLoaded).toHaveBeenCalledWith({
scope: "configured-channels",
});
});
it("narrows plugin loading from a channel-prefixed target", async () => {
await runSendAction({ channel: undefined, target: "telegram:12345" });
expect(ensurePluginRegistryLoaded).toHaveBeenCalledWith({
scope: "configured-channels",
onlyPluginIds: ["telegram"],
});
});
it("loads configured channel plugins for mixed broadcast target prefixes", async () => {
const runMessageAction = createRunMessageAction();
await expect(
runMessageAction("broadcast", {
targets: ["discord:channel:1", "telegram:123"],
message: "hi",
}),
).rejects.toThrow("exit");
expect(ensurePluginRegistryLoaded).toHaveBeenCalledWith({
scope: "configured-channels",
});
});
it("runs gateway_stop hooks before exit when registered", async () => {
hasHooksMock.mockReturnValueOnce(true);
await runSendAction();

View File

@@ -1,4 +1,5 @@
import type { Command } from "commander";
import { resolveMessageSecretScope } from "../../../cli/message-secret-scope.js";
import { messageCommand } from "../../../commands/message.js";
import { danger, setVerbose } from "../../../globals.js";
import { CHANNEL_TARGET_DESCRIPTION } from "../../../infra/outbound/channel-target.js";
@@ -6,7 +7,7 @@ import { runGlobalGatewayStopSafely } from "../../../plugins/hook-runner-global.
import { defaultRuntime } from "../../../runtime.js";
import { runCommandWithRuntime } from "../../cli-utils.js";
import { createDefaultDeps } from "../../deps.js";
import { ensurePluginRegistryLoaded } from "../../plugin-registry.js";
import { ensurePluginRegistryLoaded, type PluginRegistryScope } from "../../plugin-registry.js";
export type MessageCliHelpers = {
withMessageBase: (command: Command) => Command;
@@ -31,6 +32,20 @@ async function runPluginStopHooks(): Promise<void> {
});
}
function resolveMessagePluginLoadOptions(
opts: Record<string, unknown>,
): { scope: PluginRegistryScope; onlyPluginIds?: string[] } | undefined {
const scopedChannel = resolveMessageSecretScope({
channel: opts.channel,
target: opts.target,
targets: opts.targets,
}).channel;
if (scopedChannel) {
return { scope: "configured-channels", onlyPluginIds: [scopedChannel] };
}
return { scope: "configured-channels" };
}
export function createMessageCliHelpers(
message: Command,
messageChannelOptions: string,
@@ -50,7 +65,7 @@ export function createMessageCliHelpers(
const runMessageAction = async (action: string, opts: Record<string, unknown>) => {
setVerbose(Boolean(opts.verbose));
ensurePluginRegistryLoaded();
ensurePluginRegistryLoaded(resolveMessagePluginLoadOptions(opts));
const deps = createDefaultDeps();
let failed = false;
await runCommandWithRuntime(