mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:20:43 +00:00
test: avoid eager message action plugin discovery
Skip bundled channel discovery for plain message-action params and only resolve plugin-owned media params when an extension field is actually present. This keeps normal sends on the lightweight path while preserving plugin media-field coverage.
This commit is contained in:
57
src/infra/outbound/message-action-param-keys.ts
Normal file
57
src/infra/outbound/message-action-param-keys.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { normalizeOptionalString } from "../../shared/string-coerce.js";
|
||||
|
||||
const STANDARD_MESSAGE_ACTION_PARAM_KEYS = new Set([
|
||||
"accountId",
|
||||
"asDocument",
|
||||
"base64",
|
||||
"bestEffort",
|
||||
"blocks",
|
||||
"buttons",
|
||||
"caption",
|
||||
"card",
|
||||
"channel",
|
||||
"channelId",
|
||||
"components",
|
||||
"contentType",
|
||||
"dryRun",
|
||||
"filePath",
|
||||
"fileUrl",
|
||||
"filename",
|
||||
"forceDocument",
|
||||
"gifPlayback",
|
||||
"image",
|
||||
"interactive",
|
||||
"media",
|
||||
"mediaUrl",
|
||||
"message",
|
||||
"mimeType",
|
||||
"path",
|
||||
"pollAnonymous",
|
||||
"pollDurationHours",
|
||||
"pollMulti",
|
||||
"pollOption",
|
||||
"pollPublic",
|
||||
"pollQuestion",
|
||||
"replyTo",
|
||||
"silent",
|
||||
"target",
|
||||
"targets",
|
||||
"text",
|
||||
"threadId",
|
||||
"to",
|
||||
]);
|
||||
|
||||
export function hasPotentialPluginActionParam(params: Record<string, unknown>): boolean {
|
||||
return Object.entries(params).some(([key, value]) => {
|
||||
if (STANDARD_MESSAGE_ACTION_PARAM_KEYS.has(key)) {
|
||||
return false;
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
return Boolean(normalizeOptionalString(value));
|
||||
}
|
||||
if (typeof value === "number") {
|
||||
return Number.isFinite(value);
|
||||
}
|
||||
return value !== undefined;
|
||||
});
|
||||
}
|
||||
@@ -1,13 +1,23 @@
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
|
||||
const { resolveChannelMessageToolMediaSourceParamKeysMock } = vi.hoisted(() => ({
|
||||
resolveChannelMessageToolMediaSourceParamKeysMock: vi.fn(() => ["avatarPath", "avatarUrl"]),
|
||||
}));
|
||||
|
||||
vi.mock("../../channels/plugins/message-action-discovery.js", () => ({
|
||||
resolveChannelMessageToolMediaSourceParamKeys: resolveChannelMessageToolMediaSourceParamKeysMock,
|
||||
}));
|
||||
|
||||
import {
|
||||
collectActionMediaSourceHints,
|
||||
hydrateAttachmentParamsForAction,
|
||||
normalizeSandboxMediaList,
|
||||
normalizeSandboxMediaParams,
|
||||
resolveExtraActionMediaSourceParamKeys,
|
||||
resolveAttachmentMediaPolicy,
|
||||
} from "./message-action-params.js";
|
||||
|
||||
@@ -16,6 +26,52 @@ const maybeIt = process.platform === "win32" ? it.skip : it;
|
||||
const matrixMediaSourceParamKeys = ["avatarPath", "avatarUrl"] as const;
|
||||
|
||||
describe("message action media helpers", () => {
|
||||
beforeEach(() => {
|
||||
resolveChannelMessageToolMediaSourceParamKeysMock.mockClear();
|
||||
});
|
||||
|
||||
it("skips plugin media discovery when args only use standard action params", () => {
|
||||
expect(
|
||||
resolveExtraActionMediaSourceParamKeys({
|
||||
cfg,
|
||||
action: "send",
|
||||
channel: "slack",
|
||||
args: {
|
||||
channel: "slack",
|
||||
target: "#C12345678",
|
||||
message: "hi",
|
||||
media: "https://example.com/photo.png",
|
||||
},
|
||||
}),
|
||||
).toEqual([]);
|
||||
expect(resolveChannelMessageToolMediaSourceParamKeysMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("discovers plugin media params when args include an extension-owned field", () => {
|
||||
expect(
|
||||
resolveExtraActionMediaSourceParamKeys({
|
||||
cfg,
|
||||
action: "set-profile",
|
||||
channel: "matrix",
|
||||
args: {
|
||||
channel: "matrix",
|
||||
avatarPath: "/workspace/avatars/profile.png",
|
||||
},
|
||||
}),
|
||||
).toEqual(["avatarPath", "avatarUrl"]);
|
||||
expect(resolveChannelMessageToolMediaSourceParamKeysMock).toHaveBeenCalledWith({
|
||||
cfg,
|
||||
action: "set-profile",
|
||||
channel: "matrix",
|
||||
accountId: undefined,
|
||||
sessionKey: undefined,
|
||||
sessionId: undefined,
|
||||
agentId: undefined,
|
||||
requesterSenderId: undefined,
|
||||
senderIsOwner: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it("prefers sandbox media policy when sandbox roots are non-blank", () => {
|
||||
expect(
|
||||
resolveAttachmentMediaPolicy({
|
||||
|
||||
@@ -17,6 +17,7 @@ import { loadWebMedia } from "../../media/web-media.js";
|
||||
import { resolveSnakeCaseParamKey } from "../../param-key.js";
|
||||
import { readBooleanParam as readBooleanParamShared } from "../../plugin-sdk/boolean-param.js";
|
||||
import { normalizeOptionalString } from "../../shared/string-coerce.js";
|
||||
import { hasPotentialPluginActionParam } from "./message-action-param-keys.js";
|
||||
|
||||
export const readBooleanParam = readBooleanParamShared;
|
||||
|
||||
@@ -60,6 +61,7 @@ function buildActionMediaSourceParamKeys(extraParamKeys?: readonly string[]): st
|
||||
export function resolveExtraActionMediaSourceParamKeys(params: {
|
||||
cfg: OpenClawConfig;
|
||||
action?: ChannelMessageActionName;
|
||||
args: Record<string, unknown>;
|
||||
channel?: string;
|
||||
accountId?: string | null;
|
||||
sessionKey?: string | null;
|
||||
@@ -68,6 +70,9 @@ export function resolveExtraActionMediaSourceParamKeys(params: {
|
||||
requesterSenderId?: string | null;
|
||||
senderIsOwner?: boolean;
|
||||
}): string[] {
|
||||
if (!hasPotentialPluginActionParam(params.args)) {
|
||||
return [];
|
||||
}
|
||||
return resolveChannelMessageToolMediaSourceParamKeys({
|
||||
cfg: params.cfg,
|
||||
action: params.action,
|
||||
|
||||
@@ -860,6 +860,7 @@ export async function runMessageAction(
|
||||
const extraActionMediaSourceParamKeys = resolveExtraActionMediaSourceParamKeys({
|
||||
cfg,
|
||||
action,
|
||||
args: params,
|
||||
channel,
|
||||
accountId,
|
||||
sessionKey: input.sessionKey,
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
normalizeOptionalLowercaseString,
|
||||
normalizeOptionalString,
|
||||
} from "../../shared/string-coerce.js";
|
||||
import { hasPotentialPluginActionParam } from "./message-action-param-keys.js";
|
||||
|
||||
export type MessageActionTargetMode = "to" | "channelId" | "none";
|
||||
|
||||
@@ -84,6 +85,7 @@ const ACTION_TARGET_ALIASES: Partial<Record<ChannelMessageActionName, ActionTarg
|
||||
|
||||
function listActionTargetAliasSpecs(
|
||||
action: ChannelMessageActionName,
|
||||
params: Record<string, unknown>,
|
||||
channel?: string,
|
||||
): ActionTargetAliasSpec[] {
|
||||
const specs: ActionTargetAliasSpec[] = [];
|
||||
@@ -92,7 +94,7 @@ function listActionTargetAliasSpecs(
|
||||
specs.push(coreSpec);
|
||||
}
|
||||
const normalizedChannel = normalizeOptionalLowercaseString(channel);
|
||||
if (!normalizedChannel) {
|
||||
if (!normalizedChannel || !hasPotentialPluginActionParam(params)) {
|
||||
return specs;
|
||||
}
|
||||
const plugin = getBootstrapChannelPlugin(normalizedChannel);
|
||||
@@ -120,7 +122,7 @@ export function actionHasTarget(
|
||||
if (channelId) {
|
||||
return true;
|
||||
}
|
||||
const specs = listActionTargetAliasSpecs(action, options?.channel);
|
||||
const specs = listActionTargetAliasSpecs(action, params, options?.channel);
|
||||
if (specs.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user