feat: add message tool and CLI

This commit is contained in:
Peter Steinberger
2026-01-09 06:43:40 +01:00
parent 48a1b07097
commit db22207014
25 changed files with 763 additions and 437 deletions

View File

@@ -7,6 +7,7 @@ import { createCronTool } from "./tools/cron-tool.js";
import { createDiscordTool } from "./tools/discord-tool.js";
import { createGatewayTool } from "./tools/gateway-tool.js";
import { createImageTool } from "./tools/image-tool.js";
import { createMessageTool } from "./tools/message-tool.js";
import { createNodesTool } from "./tools/nodes-tool.js";
import { createSessionsHistoryTool } from "./tools/sessions-history-tool.js";
import { createSessionsListTool } from "./tools/sessions-list-tool.js";
@@ -35,6 +36,7 @@ export function createClawdbotTools(options?: {
createNodesTool(),
createCronTool(),
createDiscordTool(),
createMessageTool(),
createSlackTool({
agentAccountId: options?.agentAccountId,
config: options?.config,

View File

@@ -66,7 +66,7 @@ describe("createClawdbotCodingTools", () => {
it("preserves action enums in normalized schemas", () => {
const tools = createClawdbotCodingTools();
const toolNames = ["browser", "canvas", "nodes", "cron", "gateway"];
const toolNames = ["browser", "canvas", "nodes", "cron", "gateway", "message"];
const collectActionValues = (
schema: unknown,

View File

@@ -150,6 +150,14 @@
"restart": { "label": "restart", "detailKeys": ["reason", "delayMs"] }
}
},
"message": {
"emoji": "✉️",
"title": "Message",
"actions": {
"send": { "label": "send", "detailKeys": ["to", "provider", "mediaUrl"] },
"poll": { "label": "poll", "detailKeys": ["to", "provider", "question"] }
}
},
"agents_list": {
"emoji": "🧭",
"title": "Agents",

View File

@@ -0,0 +1,112 @@
import { Type } from "@sinclair/typebox";
import {
sendMessage,
sendPoll,
type MessagePollResult,
type MessageSendResult,
} from "../../infra/outbound/message.js";
import type { AnyAgentTool } from "./common.js";
import {
jsonResult,
readNumberParam,
readStringArrayParam,
readStringParam,
} from "./common.js";
const MessageToolSchema = Type.Object({
action: Type.Union([Type.Literal("send"), Type.Literal("poll")]),
to: Type.Optional(Type.String()),
content: Type.Optional(Type.String()),
mediaUrl: Type.Optional(Type.String()),
gifPlayback: Type.Optional(Type.Boolean()),
provider: Type.Optional(Type.String()),
accountId: Type.Optional(Type.String()),
dryRun: Type.Optional(Type.Boolean()),
bestEffort: Type.Optional(Type.Boolean()),
question: Type.Optional(Type.String()),
options: Type.Optional(Type.Array(Type.String())),
maxSelections: Type.Optional(Type.Number()),
durationHours: Type.Optional(Type.Number()),
gatewayUrl: Type.Optional(Type.String()),
gatewayToken: Type.Optional(Type.String()),
timeoutMs: Type.Optional(Type.Number()),
});
export function createMessageTool(): AnyAgentTool {
return {
label: "Message",
name: "message",
description:
"Send messages and polls across providers (send/poll). Prefer this for general outbound messaging.",
parameters: MessageToolSchema,
execute: async (_toolCallId, args) => {
const params = args as Record<string, unknown>;
const action = readStringParam(params, "action", { required: true });
const gateway = {
url: readStringParam(params, "gatewayUrl", { trim: false }),
token: readStringParam(params, "gatewayToken", { trim: false }),
timeoutMs: readNumberParam(params, "timeoutMs"),
clientName: "agent" as const,
mode: "agent" as const,
};
const dryRun = Boolean(params.dryRun);
if (action === "send") {
const to = readStringParam(params, "to", { required: true });
const content = readStringParam(params, "content", {
required: true,
allowEmpty: true,
});
const mediaUrl = readStringParam(params, "mediaUrl", { trim: false });
const provider = readStringParam(params, "provider");
const accountId = readStringParam(params, "accountId");
const gifPlayback =
typeof params.gifPlayback === "boolean" ? params.gifPlayback : false;
const bestEffort =
typeof params.bestEffort === "boolean" ? params.bestEffort : undefined;
const result: MessageSendResult = await sendMessage({
to,
content,
mediaUrl: mediaUrl || undefined,
provider: provider || undefined,
accountId: accountId || undefined,
gifPlayback,
dryRun,
bestEffort,
gateway,
});
return jsonResult(result);
}
if (action === "poll") {
const to = readStringParam(params, "to", { required: true });
const question = readStringParam(params, "question", { required: true });
const options =
readStringArrayParam(params, "options", { required: true }) ?? [];
const maxSelections = readNumberParam(params, "maxSelections", {
integer: true,
});
const durationHours = readNumberParam(params, "durationHours", {
integer: true,
});
const provider = readStringParam(params, "provider");
const result: MessagePollResult = await sendPoll({
to,
question,
options,
maxSelections,
durationHours,
provider: provider || undefined,
dryRun,
gateway,
});
return jsonResult(result);
}
throw new Error(`Unknown action: ${action}`);
},
};
}