mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-11 05:34:32 +00:00
fix(agents): land #20840 cross-channel message-tool actions from @altaywtf
Include scoped cross-channel action/description behavior, regression tests, changelog note, and make Ollama discovery tests URL-scoped to avoid env-dependent fetch interference. Co-authored-by: Altay <altay@hey.com>
This commit is contained in:
@@ -171,7 +171,8 @@ describe("message tool schema scoping", () => {
|
||||
expect(buttonItemProps.style).toBeDefined();
|
||||
expect(actionEnum).toContain("send");
|
||||
expect(actionEnum).toContain("react");
|
||||
expect(actionEnum).not.toContain("poll");
|
||||
// Other channels' actions are included so isolated/cron agents can use them
|
||||
expect(actionEnum).toContain("poll");
|
||||
});
|
||||
|
||||
it("shows discord components when scoped to discord", () => {
|
||||
@@ -193,11 +194,16 @@ describe("message tool schema scoping", () => {
|
||||
expect(properties.buttons).toBeUndefined();
|
||||
expect(actionEnum).toContain("send");
|
||||
expect(actionEnum).toContain("poll");
|
||||
expect(actionEnum).not.toContain("react");
|
||||
// Other channels' actions are included so isolated/cron agents can use them
|
||||
expect(actionEnum).toContain("react");
|
||||
});
|
||||
});
|
||||
|
||||
describe("message tool description", () => {
|
||||
afterEach(() => {
|
||||
setActivePluginRegistry(createTestRegistry([]));
|
||||
});
|
||||
|
||||
const bluebubblesPlugin: ChannelPlugin = {
|
||||
id: "bluebubbles",
|
||||
meta: {
|
||||
@@ -248,8 +254,78 @@ describe("message tool description", () => {
|
||||
expect(tool.description).not.toContain("addParticipant");
|
||||
expect(tool.description).not.toContain("removeParticipant");
|
||||
expect(tool.description).not.toContain("leaveGroup");
|
||||
});
|
||||
|
||||
setActivePluginRegistry(createTestRegistry([]));
|
||||
it("includes other configured channels when currentChannel is set", () => {
|
||||
const signalPlugin: ChannelPlugin = {
|
||||
id: "signal",
|
||||
meta: {
|
||||
id: "signal",
|
||||
label: "Signal",
|
||||
selectionLabel: "Signal",
|
||||
docsPath: "/channels/signal",
|
||||
blurb: "Signal test plugin.",
|
||||
},
|
||||
capabilities: { chatTypes: ["direct", "group"], media: true },
|
||||
config: {
|
||||
listAccountIds: () => ["default"],
|
||||
resolveAccount: () => ({}),
|
||||
},
|
||||
actions: {
|
||||
listActions: () => ["send", "react"] as const,
|
||||
},
|
||||
};
|
||||
|
||||
const telegramPluginFull: ChannelPlugin = {
|
||||
id: "telegram",
|
||||
meta: {
|
||||
id: "telegram",
|
||||
label: "Telegram",
|
||||
selectionLabel: "Telegram",
|
||||
docsPath: "/channels/telegram",
|
||||
blurb: "Telegram test plugin.",
|
||||
},
|
||||
capabilities: { chatTypes: ["direct", "group"], media: true },
|
||||
config: {
|
||||
listAccountIds: () => ["default"],
|
||||
resolveAccount: () => ({}),
|
||||
},
|
||||
actions: {
|
||||
listActions: () => ["send", "react", "delete", "edit", "topic-create"] as const,
|
||||
},
|
||||
};
|
||||
|
||||
setActivePluginRegistry(
|
||||
createTestRegistry([
|
||||
{ pluginId: "signal", source: "test", plugin: signalPlugin },
|
||||
{ pluginId: "telegram", source: "test", plugin: telegramPluginFull },
|
||||
]),
|
||||
);
|
||||
|
||||
const tool = createMessageTool({
|
||||
config: {} as never,
|
||||
currentChannelProvider: "signal",
|
||||
});
|
||||
|
||||
// Current channel actions are listed
|
||||
expect(tool.description).toContain("Current channel (signal) supports: react, send.");
|
||||
// Other configured channels are also listed
|
||||
expect(tool.description).toContain("Other configured channels:");
|
||||
expect(tool.description).toContain("telegram (delete, edit, react, send, topic-create)");
|
||||
});
|
||||
|
||||
it("does not include 'Other configured channels' when only one channel is configured", () => {
|
||||
setActivePluginRegistry(
|
||||
createTestRegistry([{ pluginId: "bluebubbles", source: "test", plugin: bluebubblesPlugin }]),
|
||||
);
|
||||
|
||||
const tool = createMessageTool({
|
||||
config: {} as never,
|
||||
currentChannelProvider: "bluebubbles",
|
||||
});
|
||||
|
||||
expect(tool.description).toContain("Current channel (bluebubbles) supports:");
|
||||
expect(tool.description).not.toContain("Other configured channels");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import { BLUEBUBBLES_GROUP_ACTIONS } from "../../channels/plugins/bluebubbles-actions.js";
|
||||
import { listChannelPlugins } from "../../channels/plugins/index.js";
|
||||
import {
|
||||
listChannelMessageActions,
|
||||
supportsChannelMessageButtons,
|
||||
@@ -460,8 +461,18 @@ function resolveMessageToolSchemaActions(params: {
|
||||
channel: currentChannel,
|
||||
currentChannelId: params.currentChannelId,
|
||||
});
|
||||
const withSend = new Set<string>(["send", ...scopedActions]);
|
||||
return Array.from(withSend);
|
||||
const allActions = new Set<string>(["send", ...scopedActions]);
|
||||
// Include actions from other configured channels so isolated/cron agents
|
||||
// can invoke cross-channel actions without validation errors.
|
||||
for (const plugin of listChannelPlugins()) {
|
||||
if (plugin.id === currentChannel) {
|
||||
continue;
|
||||
}
|
||||
for (const action of listChannelSupportedActions({ cfg: params.cfg, channel: plugin.id })) {
|
||||
allActions.add(action);
|
||||
}
|
||||
}
|
||||
return Array.from(allActions);
|
||||
}
|
||||
const actions = listChannelMessageActions(params.cfg);
|
||||
return actions.length > 0 ? actions : ["send"];
|
||||
@@ -542,7 +553,7 @@ function buildMessageToolDescription(options?: {
|
||||
}): string {
|
||||
const baseDescription = "Send, delete, and manage messages via channel plugins.";
|
||||
|
||||
// If we have a current channel, show only its supported actions
|
||||
// If we have a current channel, show its actions and list other configured channels
|
||||
if (options?.currentChannel) {
|
||||
const channelActions = filterActionsForContext({
|
||||
actions: listChannelSupportedActions({
|
||||
@@ -556,7 +567,25 @@ function buildMessageToolDescription(options?: {
|
||||
// Always include "send" as a base action
|
||||
const allActions = new Set(["send", ...channelActions]);
|
||||
const actionList = Array.from(allActions).toSorted().join(", ");
|
||||
return `${baseDescription} Current channel (${options.currentChannel}) supports: ${actionList}.`;
|
||||
let desc = `${baseDescription} Current channel (${options.currentChannel}) supports: ${actionList}.`;
|
||||
|
||||
// Include other configured channels so cron/isolated agents can discover them
|
||||
const otherChannels: string[] = [];
|
||||
for (const plugin of listChannelPlugins()) {
|
||||
if (plugin.id === options.currentChannel) {
|
||||
continue;
|
||||
}
|
||||
const actions = listChannelSupportedActions({ cfg: options.config, channel: plugin.id });
|
||||
if (actions.length > 0) {
|
||||
const all = new Set(["send", ...actions]);
|
||||
otherChannels.push(`${plugin.id} (${Array.from(all).toSorted().join(", ")})`);
|
||||
}
|
||||
}
|
||||
if (otherChannels.length > 0) {
|
||||
desc += ` Other configured channels: ${otherChannels.join(", ")}.`;
|
||||
}
|
||||
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user