feat: scope telegram inline buttons
This commit is contained in:
@@ -8,6 +8,7 @@ import type { ReasoningLevel, ThinkLevel } from "../../auto-reply/thinking.js";
|
||||
import { resolveChannelCapabilities } from "../../config/channel-capabilities.js";
|
||||
import type { ClawdbotConfig } from "../../config/config.js";
|
||||
import { getMachineDisplayName } from "../../infra/machine-name.js";
|
||||
import { resolveTelegramInlineButtonsScope } from "../../telegram/inline-buttons.js";
|
||||
import { type enqueueCommand, enqueueCommandInLane } from "../../process/command-queue.js";
|
||||
import { normalizeMessageChannel } from "../../utils/message-channel.js";
|
||||
import { isSubagentSessionKey } from "../../routing/session-key.js";
|
||||
@@ -210,13 +211,29 @@ export async function compactEmbeddedPiSession(params: {
|
||||
const runtimeChannel = normalizeMessageChannel(
|
||||
params.messageChannel ?? params.messageProvider,
|
||||
);
|
||||
const runtimeCapabilities = runtimeChannel
|
||||
let runtimeCapabilities = runtimeChannel
|
||||
? (resolveChannelCapabilities({
|
||||
cfg: params.config,
|
||||
channel: runtimeChannel,
|
||||
accountId: params.agentAccountId,
|
||||
}) ?? [])
|
||||
: undefined;
|
||||
if (runtimeChannel === "telegram" && params.config) {
|
||||
const inlineButtonsScope = resolveTelegramInlineButtonsScope({
|
||||
cfg: params.config,
|
||||
accountId: params.agentAccountId ?? undefined,
|
||||
});
|
||||
if (inlineButtonsScope !== "off") {
|
||||
if (!runtimeCapabilities) runtimeCapabilities = [];
|
||||
if (
|
||||
!runtimeCapabilities.some(
|
||||
(cap) => String(cap).trim().toLowerCase() === "inlinebuttons",
|
||||
)
|
||||
) {
|
||||
runtimeCapabilities.push("inlineButtons");
|
||||
}
|
||||
}
|
||||
}
|
||||
const runtimeInfo = {
|
||||
host: machineName,
|
||||
os: `${os.type()} ${os.release()}`,
|
||||
|
||||
@@ -9,6 +9,7 @@ import { createAgentSession, SessionManager, SettingsManager } from "@mariozechn
|
||||
import { resolveHeartbeatPrompt } from "../../../auto-reply/heartbeat.js";
|
||||
import { resolveChannelCapabilities } from "../../../config/channel-capabilities.js";
|
||||
import { getMachineDisplayName } from "../../../infra/machine-name.js";
|
||||
import { resolveTelegramInlineButtonsScope } from "../../../telegram/inline-buttons.js";
|
||||
import { resolveTelegramReactionLevel } from "../../../telegram/reaction-level.js";
|
||||
import { normalizeMessageChannel } from "../../../utils/message-channel.js";
|
||||
import { isReasoningTagProvider } from "../../../utils/provider-utils.js";
|
||||
@@ -157,13 +158,27 @@ export async function runEmbeddedAttempt(
|
||||
|
||||
const machineName = await getMachineDisplayName();
|
||||
const runtimeChannel = normalizeMessageChannel(params.messageChannel ?? params.messageProvider);
|
||||
const runtimeCapabilities = runtimeChannel
|
||||
let runtimeCapabilities = runtimeChannel
|
||||
? (resolveChannelCapabilities({
|
||||
cfg: params.config,
|
||||
channel: runtimeChannel,
|
||||
accountId: params.agentAccountId,
|
||||
}) ?? [])
|
||||
: undefined;
|
||||
if (runtimeChannel === "telegram" && params.config) {
|
||||
const inlineButtonsScope = resolveTelegramInlineButtonsScope({
|
||||
cfg: params.config,
|
||||
accountId: params.agentAccountId ?? undefined,
|
||||
});
|
||||
if (inlineButtonsScope !== "off") {
|
||||
if (!runtimeCapabilities) runtimeCapabilities = [];
|
||||
if (
|
||||
!runtimeCapabilities.some((cap) => String(cap).trim().toLowerCase() === "inlinebuttons")
|
||||
) {
|
||||
runtimeCapabilities.push("inlineButtons");
|
||||
}
|
||||
}
|
||||
}
|
||||
const reactionGuidance =
|
||||
runtimeChannel === "telegram" && params.config
|
||||
? (() => {
|
||||
|
||||
@@ -98,7 +98,7 @@ function buildMessagingSection(params: {
|
||||
params.inlineButtonsEnabled
|
||||
? "- Inline buttons supported. Use `action=send` with `buttons=[[{text,callback_data}]]` (callback_data routes back as a user message)."
|
||||
: params.runtimeChannel
|
||||
? `- Inline buttons not enabled for ${params.runtimeChannel}. If you need them, ask to add "inlineButtons" to ${params.runtimeChannel}.capabilities or ${params.runtimeChannel}.accounts.<id>.capabilities.`
|
||||
? `- Inline buttons not enabled for ${params.runtimeChannel}. If you need them, ask to set ${params.runtimeChannel}.capabilities.inlineButtons ("dm"|"group"|"all"|"allowlist").`
|
||||
: "",
|
||||
]
|
||||
.filter(Boolean)
|
||||
|
||||
@@ -328,10 +328,28 @@ describe("handleTelegramAction", () => {
|
||||
).rejects.toThrow(/Telegram bot token missing/);
|
||||
});
|
||||
|
||||
it("requires inlineButtons capability when buttons are provided", async () => {
|
||||
it("allows inline buttons by default (allowlist)", async () => {
|
||||
const cfg = {
|
||||
channels: { telegram: { botToken: "tok" } },
|
||||
} as ClawdbotConfig;
|
||||
await handleTelegramAction(
|
||||
{
|
||||
action: "sendMessage",
|
||||
to: "@testchannel",
|
||||
content: "Choose",
|
||||
buttons: [[{ text: "Ok", callback_data: "cmd:ok" }]],
|
||||
},
|
||||
cfg,
|
||||
);
|
||||
expect(sendMessageTelegram).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("blocks inline buttons when scope is off", async () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
telegram: { botToken: "tok", capabilities: { inlineButtons: "off" } },
|
||||
},
|
||||
} as ClawdbotConfig;
|
||||
await expect(
|
||||
handleTelegramAction(
|
||||
{
|
||||
@@ -342,13 +360,32 @@ describe("handleTelegramAction", () => {
|
||||
},
|
||||
cfg,
|
||||
),
|
||||
).rejects.toThrow(/inlineButtons/i);
|
||||
).rejects.toThrow(/inline buttons are disabled/i);
|
||||
});
|
||||
|
||||
it("blocks inline buttons in groups when scope is dm", async () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
telegram: { botToken: "tok", capabilities: { inlineButtons: "dm" } },
|
||||
},
|
||||
} as ClawdbotConfig;
|
||||
await expect(
|
||||
handleTelegramAction(
|
||||
{
|
||||
action: "sendMessage",
|
||||
to: "-100123456",
|
||||
content: "Choose",
|
||||
buttons: [[{ text: "Ok", callback_data: "cmd:ok" }]],
|
||||
},
|
||||
cfg,
|
||||
),
|
||||
).rejects.toThrow(/inline buttons are limited to DMs/i);
|
||||
});
|
||||
|
||||
it("sends messages with inline keyboard buttons when enabled", async () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
telegram: { botToken: "tok", capabilities: ["inlineButtons"] },
|
||||
telegram: { botToken: "tok", capabilities: { inlineButtons: "all" } },
|
||||
},
|
||||
} as ClawdbotConfig;
|
||||
await handleTelegramAction(
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
||||
import { resolveChannelCapabilities } from "../../config/channel-capabilities.js";
|
||||
import type { ClawdbotConfig } from "../../config/config.js";
|
||||
import { resolveTelegramReactionLevel } from "../../telegram/reaction-level.js";
|
||||
import {
|
||||
@@ -8,6 +7,10 @@ import {
|
||||
sendMessageTelegram,
|
||||
} from "../../telegram/send.js";
|
||||
import { resolveTelegramToken } from "../../telegram/token.js";
|
||||
import {
|
||||
resolveTelegramInlineButtonsScope,
|
||||
resolveTelegramTargetChatType,
|
||||
} from "../../telegram/inline-buttons.js";
|
||||
import {
|
||||
createActionGate,
|
||||
jsonResult,
|
||||
@@ -22,19 +25,6 @@ type TelegramButton = {
|
||||
callback_data: string;
|
||||
};
|
||||
|
||||
function hasInlineButtonsCapability(params: {
|
||||
cfg: ClawdbotConfig;
|
||||
accountId?: string | undefined;
|
||||
}): boolean {
|
||||
const caps =
|
||||
resolveChannelCapabilities({
|
||||
cfg: params.cfg,
|
||||
channel: "telegram",
|
||||
accountId: params.accountId,
|
||||
}) ?? [];
|
||||
return caps.some((cap) => cap.toLowerCase() === "inlinebuttons");
|
||||
}
|
||||
|
||||
export function readTelegramButtons(
|
||||
params: Record<string, unknown>,
|
||||
): TelegramButton[][] | undefined {
|
||||
@@ -138,10 +128,32 @@ export async function handleTelegramAction(
|
||||
allowEmpty: true,
|
||||
}) ?? "";
|
||||
const buttons = readTelegramButtons(params);
|
||||
if (buttons && !hasInlineButtonsCapability({ cfg, accountId: accountId ?? undefined })) {
|
||||
throw new Error(
|
||||
'Telegram inline buttons requested but not enabled. Add "inlineButtons" to channels.telegram.capabilities (or channels.telegram.accounts.<id>.capabilities).',
|
||||
);
|
||||
if (buttons) {
|
||||
const inlineButtonsScope = resolveTelegramInlineButtonsScope({
|
||||
cfg,
|
||||
accountId: accountId ?? undefined,
|
||||
});
|
||||
if (inlineButtonsScope === "off") {
|
||||
throw new Error(
|
||||
'Telegram inline buttons are disabled. Set channels.telegram.capabilities.inlineButtons to "dm", "group", "all", or "allowlist".',
|
||||
);
|
||||
}
|
||||
if (inlineButtonsScope === "dm" || inlineButtonsScope === "group") {
|
||||
const targetType = resolveTelegramTargetChatType(to);
|
||||
if (targetType === "unknown") {
|
||||
throw new Error(
|
||||
`Telegram inline buttons require a numeric chat id when inlineButtons="${inlineButtonsScope}".`,
|
||||
);
|
||||
}
|
||||
if (inlineButtonsScope === "dm" && targetType !== "direct") {
|
||||
throw new Error('Telegram inline buttons are limited to DMs when inlineButtons="dm".');
|
||||
}
|
||||
if (inlineButtonsScope === "group" && targetType !== "group") {
|
||||
throw new Error(
|
||||
'Telegram inline buttons are limited to groups when inlineButtons="group".',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Optional threading parameters for forum topics and reply chains
|
||||
const replyToMessageId = readNumberParam(params, "replyToMessageId", {
|
||||
|
||||
Reference in New Issue
Block a user