talon-agent 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -1
- package/src/__tests__/compose-tools.test.ts +216 -0
- package/src/__tests__/fuzz.test.ts +0 -2
- package/src/__tests__/gateway-actions.test.ts +1 -423
- package/src/backend/claude-sdk/index.ts +39 -54
- package/src/backend/opencode/index.ts +5 -20
- package/src/bootstrap.ts +70 -6
- package/src/core/gateway-actions.ts +0 -87
- package/src/core/tools/bridge.ts +40 -0
- package/src/core/tools/chat.ts +52 -0
- package/src/core/tools/history.ts +80 -0
- package/src/core/tools/index.ts +82 -0
- package/src/core/tools/mcp-server.ts +64 -0
- package/src/core/tools/media.ts +23 -0
- package/src/core/tools/members.ts +46 -0
- package/src/core/tools/messaging.ts +300 -0
- package/src/core/tools/scheduling.ts +89 -0
- package/src/core/tools/stickers.ts +143 -0
- package/src/core/tools/types.ts +60 -0
- package/src/core/tools/web.ts +26 -0
- package/src/frontend/telegram/actions.ts +10 -1
- package/src/plugins/github/index.ts +106 -0
- package/src/plugins/playwright/index.ts +82 -0
- package/src/storage/sessions.ts +0 -10
- package/src/util/config.ts +20 -1
- package/src/util/log.ts +3 -1
- package/src/backend/claude-sdk/tools.ts +0 -651
- package/src/frontend/teams/tools.ts +0 -175
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Messaging tools — send, react, edit, delete, forward, pin/unpin, stop poll.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import type { ToolDefinition } from "./types.js";
|
|
7
|
+
|
|
8
|
+
export const messagingTools: ToolDefinition[] = [
|
|
9
|
+
// ── Telegram unified send ─────────────────────────────────────────────
|
|
10
|
+
{
|
|
11
|
+
name: "send",
|
|
12
|
+
description: `Send content to the current Telegram chat. Supports text, photos, videos, files, audio, voice, stickers, polls, locations, contacts, dice, and GIFs.
|
|
13
|
+
|
|
14
|
+
Examples:
|
|
15
|
+
Text: send(type="text", text="Hello!")
|
|
16
|
+
Reply: send(type="text", text="Yes!", reply_to=12345)
|
|
17
|
+
With buttons: send(type="text", text="Pick one", buttons=[[{"text":"A","callback_data":"a"}]])
|
|
18
|
+
Photo: send(type="photo", file_path="/path/to/img.jpg", caption="Look!")
|
|
19
|
+
File: send(type="file", file_path="/path/to/report.pdf")
|
|
20
|
+
Audio: send(type="audio", file_path="/path/to/song.mp3", title="Song Name", performer="Artist")
|
|
21
|
+
Poll: send(type="poll", question="Best language?", options=["Rust","Go","TS"])
|
|
22
|
+
Dice: send(type="dice")
|
|
23
|
+
Location: send(type="location", latitude=37.7749, longitude=-122.4194)
|
|
24
|
+
Sticker: send(type="sticker", file_id="CAACAgI...")`,
|
|
25
|
+
schema: {
|
|
26
|
+
type: z
|
|
27
|
+
.enum([
|
|
28
|
+
"text",
|
|
29
|
+
"photo",
|
|
30
|
+
"file",
|
|
31
|
+
"video",
|
|
32
|
+
"voice",
|
|
33
|
+
"audio",
|
|
34
|
+
"animation",
|
|
35
|
+
"sticker",
|
|
36
|
+
"poll",
|
|
37
|
+
"location",
|
|
38
|
+
"contact",
|
|
39
|
+
"dice",
|
|
40
|
+
])
|
|
41
|
+
.describe("Content type to send"),
|
|
42
|
+
text: z
|
|
43
|
+
.string()
|
|
44
|
+
.optional()
|
|
45
|
+
.describe("Message text (for type=text). Supports Markdown."),
|
|
46
|
+
reply_to: z.number().optional().describe("Message ID to reply to"),
|
|
47
|
+
file_path: z
|
|
48
|
+
.string()
|
|
49
|
+
.optional()
|
|
50
|
+
.describe("Workspace file path (for photo/file/video/voice/animation)"),
|
|
51
|
+
file_id: z.string().optional().describe("Telegram file_id (for sticker)"),
|
|
52
|
+
caption: z.string().optional().describe("Caption for media"),
|
|
53
|
+
buttons: z
|
|
54
|
+
.array(
|
|
55
|
+
z.array(
|
|
56
|
+
z.object({
|
|
57
|
+
text: z.string(),
|
|
58
|
+
url: z.string().optional(),
|
|
59
|
+
callback_data: z.string().optional(),
|
|
60
|
+
}),
|
|
61
|
+
),
|
|
62
|
+
)
|
|
63
|
+
.optional()
|
|
64
|
+
.describe("Inline keyboard button rows"),
|
|
65
|
+
question: z.string().optional().describe("Poll question"),
|
|
66
|
+
options: z.array(z.string()).optional().describe("Poll options"),
|
|
67
|
+
is_anonymous: z.boolean().optional().describe("Anonymous poll"),
|
|
68
|
+
correct_option_id: z
|
|
69
|
+
.number()
|
|
70
|
+
.optional()
|
|
71
|
+
.describe("Quiz correct answer index"),
|
|
72
|
+
explanation: z.string().optional().describe("Quiz explanation"),
|
|
73
|
+
latitude: z.number().optional().describe("Location latitude"),
|
|
74
|
+
longitude: z.number().optional().describe("Location longitude"),
|
|
75
|
+
phone_number: z.string().optional().describe("Contact phone"),
|
|
76
|
+
first_name: z.string().optional().describe("Contact first name"),
|
|
77
|
+
last_name: z.string().optional().describe("Contact last name"),
|
|
78
|
+
title: z.string().optional().describe("Audio title (for type=audio)"),
|
|
79
|
+
performer: z
|
|
80
|
+
.string()
|
|
81
|
+
.optional()
|
|
82
|
+
.describe("Audio performer/artist (for type=audio)"),
|
|
83
|
+
emoji: z.string().optional().describe("Dice emoji (🎲🎯🏀⚽🎳🎰)"),
|
|
84
|
+
delay_seconds: z
|
|
85
|
+
.number()
|
|
86
|
+
.optional()
|
|
87
|
+
.describe("Schedule: delay before sending (1-3600)"),
|
|
88
|
+
},
|
|
89
|
+
execute: async (params, bridge) => {
|
|
90
|
+
const { type } = params;
|
|
91
|
+
switch (type) {
|
|
92
|
+
case "text": {
|
|
93
|
+
if (params.delay_seconds) {
|
|
94
|
+
return bridge("schedule_message", {
|
|
95
|
+
text: params.text,
|
|
96
|
+
delay_seconds: params.delay_seconds,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
if (params.buttons) {
|
|
100
|
+
return bridge("send_message_with_buttons", {
|
|
101
|
+
text: params.text,
|
|
102
|
+
rows: params.buttons,
|
|
103
|
+
reply_to_message_id: params.reply_to,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
return bridge("send_message", {
|
|
107
|
+
text: params.text,
|
|
108
|
+
reply_to_message_id: params.reply_to,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
case "photo":
|
|
112
|
+
return bridge("send_photo", {
|
|
113
|
+
file_path: params.file_path,
|
|
114
|
+
caption: params.caption,
|
|
115
|
+
reply_to: params.reply_to,
|
|
116
|
+
});
|
|
117
|
+
case "file":
|
|
118
|
+
return bridge("send_file", {
|
|
119
|
+
file_path: params.file_path,
|
|
120
|
+
caption: params.caption,
|
|
121
|
+
reply_to: params.reply_to,
|
|
122
|
+
});
|
|
123
|
+
case "video":
|
|
124
|
+
return bridge("send_video", {
|
|
125
|
+
file_path: params.file_path,
|
|
126
|
+
caption: params.caption,
|
|
127
|
+
reply_to: params.reply_to,
|
|
128
|
+
});
|
|
129
|
+
case "voice":
|
|
130
|
+
return bridge("send_voice", {
|
|
131
|
+
file_path: params.file_path,
|
|
132
|
+
caption: params.caption,
|
|
133
|
+
reply_to: params.reply_to,
|
|
134
|
+
});
|
|
135
|
+
case "audio":
|
|
136
|
+
return bridge("send_audio", {
|
|
137
|
+
file_path: params.file_path,
|
|
138
|
+
caption: params.caption,
|
|
139
|
+
title: params.title,
|
|
140
|
+
performer: params.performer,
|
|
141
|
+
reply_to: params.reply_to,
|
|
142
|
+
});
|
|
143
|
+
case "animation":
|
|
144
|
+
return bridge("send_animation", {
|
|
145
|
+
file_path: params.file_path,
|
|
146
|
+
caption: params.caption,
|
|
147
|
+
reply_to: params.reply_to,
|
|
148
|
+
});
|
|
149
|
+
case "sticker":
|
|
150
|
+
return bridge("send_sticker", {
|
|
151
|
+
file_id: params.file_id,
|
|
152
|
+
reply_to: params.reply_to,
|
|
153
|
+
});
|
|
154
|
+
case "poll":
|
|
155
|
+
return bridge("send_poll", {
|
|
156
|
+
question: params.question,
|
|
157
|
+
options: params.options,
|
|
158
|
+
is_anonymous: params.is_anonymous,
|
|
159
|
+
correct_option_id: params.correct_option_id,
|
|
160
|
+
explanation: params.explanation,
|
|
161
|
+
type: params.correct_option_id !== undefined ? "quiz" : "regular",
|
|
162
|
+
});
|
|
163
|
+
case "location":
|
|
164
|
+
return bridge("send_location", {
|
|
165
|
+
latitude: params.latitude,
|
|
166
|
+
longitude: params.longitude,
|
|
167
|
+
});
|
|
168
|
+
case "contact":
|
|
169
|
+
return bridge("send_contact", {
|
|
170
|
+
phone_number: params.phone_number,
|
|
171
|
+
first_name: params.first_name,
|
|
172
|
+
last_name: params.last_name,
|
|
173
|
+
});
|
|
174
|
+
case "dice":
|
|
175
|
+
return bridge("send_dice", { emoji: params.emoji });
|
|
176
|
+
default:
|
|
177
|
+
return { ok: false, error: `Unknown type: ${type}` };
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
frontends: ["telegram"],
|
|
181
|
+
tag: "messaging",
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
// ── Teams send_message ────────────────────────────────────────────────
|
|
185
|
+
{
|
|
186
|
+
name: "send_message",
|
|
187
|
+
description: `Send a message to the Teams chat. Supports Markdown formatting.
|
|
188
|
+
|
|
189
|
+
Examples:
|
|
190
|
+
send_message(text="Hello!")
|
|
191
|
+
send_message(text="Here's a **bold** message with \`code\`")`,
|
|
192
|
+
schema: {
|
|
193
|
+
text: z.string().describe("Message text. Supports Markdown."),
|
|
194
|
+
},
|
|
195
|
+
execute: (params, bridge) => bridge("send_message", params),
|
|
196
|
+
frontends: ["teams"],
|
|
197
|
+
tag: "messaging",
|
|
198
|
+
},
|
|
199
|
+
|
|
200
|
+
// ── Teams send_message_with_buttons ───────────────────────────────────
|
|
201
|
+
{
|
|
202
|
+
name: "send_message_with_buttons",
|
|
203
|
+
description: `Send a message with clickable link buttons. Buttons appear below the message as Adaptive Card actions.
|
|
204
|
+
|
|
205
|
+
Example: send_message_with_buttons(text="Choose:", rows=[[{"text":"Docs","url":"https://..."}]])`,
|
|
206
|
+
schema: {
|
|
207
|
+
text: z.string().describe("Message text"),
|
|
208
|
+
rows: z
|
|
209
|
+
.array(
|
|
210
|
+
z.array(
|
|
211
|
+
z.object({
|
|
212
|
+
text: z.string().describe("Button label"),
|
|
213
|
+
url: z.string().optional().describe("URL to open when clicked"),
|
|
214
|
+
}),
|
|
215
|
+
),
|
|
216
|
+
)
|
|
217
|
+
.describe("Button rows"),
|
|
218
|
+
},
|
|
219
|
+
execute: (params, bridge) => bridge("send_message_with_buttons", params),
|
|
220
|
+
frontends: ["teams"],
|
|
221
|
+
tag: "messaging",
|
|
222
|
+
},
|
|
223
|
+
|
|
224
|
+
// ── react ─────────────────────────────────────────────────────────────
|
|
225
|
+
{
|
|
226
|
+
name: "react",
|
|
227
|
+
description:
|
|
228
|
+
"Add an emoji reaction to a message. Valid: 👍 👎 ❤ 🔥 🥰 👏 😁 🤔 🤯 😱 🤬 😢 🎉 🤩 🤮 💩 🙏 👌 🕊 🤡 🥱 🥴 😍 🐳 ❤🔥 🌚 🌭 💯 🤣 ⚡ 🍌 🏆 💔 🤨 😐 🍓 🍾 💋 🖕 😈 😴 😭 🤓 👻 👨💻 👀 🎃 🙈 😇 😨 🤝 ✍ 🤗 🫡 🎅 🎄 ☃ 💅 🤪 🗿 🆒 💘 🙉 🦄 😘 💊 🙊 😎 👾 🤷 🤷♂ 🤷♀ 😡",
|
|
229
|
+
schema: {
|
|
230
|
+
message_id: z.number().describe("Message ID"),
|
|
231
|
+
emoji: z.string().describe("Reaction emoji"),
|
|
232
|
+
},
|
|
233
|
+
execute: (params, bridge) => bridge("react", params),
|
|
234
|
+
frontends: ["telegram"],
|
|
235
|
+
tag: "messaging",
|
|
236
|
+
},
|
|
237
|
+
|
|
238
|
+
// ── edit_message ──────────────────────────────────────────────────────
|
|
239
|
+
{
|
|
240
|
+
name: "edit_message",
|
|
241
|
+
description: "Edit a previously sent message.",
|
|
242
|
+
schema: { message_id: z.number(), text: z.string() },
|
|
243
|
+
execute: (params, bridge) => bridge("edit_message", params),
|
|
244
|
+
frontends: ["telegram"],
|
|
245
|
+
tag: "messaging",
|
|
246
|
+
},
|
|
247
|
+
|
|
248
|
+
// ── delete_message ────────────────────────────────────────────────────
|
|
249
|
+
{
|
|
250
|
+
name: "delete_message",
|
|
251
|
+
description: "Delete a message.",
|
|
252
|
+
schema: { message_id: z.number() },
|
|
253
|
+
execute: (params, bridge) => bridge("delete_message", params),
|
|
254
|
+
frontends: ["telegram"],
|
|
255
|
+
tag: "messaging",
|
|
256
|
+
},
|
|
257
|
+
|
|
258
|
+
// ── forward_message ───────────────────────────────────────────────────
|
|
259
|
+
{
|
|
260
|
+
name: "forward_message",
|
|
261
|
+
description: "Forward a message within the chat.",
|
|
262
|
+
schema: { message_id: z.number() },
|
|
263
|
+
execute: (params, bridge) => bridge("forward_message", params),
|
|
264
|
+
frontends: ["telegram"],
|
|
265
|
+
tag: "messaging",
|
|
266
|
+
},
|
|
267
|
+
|
|
268
|
+
// ── pin_message ───────────────────────────────────────────────────────
|
|
269
|
+
{
|
|
270
|
+
name: "pin_message",
|
|
271
|
+
description: "Pin a message.",
|
|
272
|
+
schema: { message_id: z.number() },
|
|
273
|
+
execute: (params, bridge) => bridge("pin_message", params),
|
|
274
|
+
frontends: ["telegram"],
|
|
275
|
+
tag: "messaging",
|
|
276
|
+
},
|
|
277
|
+
|
|
278
|
+
// ── unpin_message ─────────────────────────────────────────────────────
|
|
279
|
+
{
|
|
280
|
+
name: "unpin_message",
|
|
281
|
+
description: "Unpin a message.",
|
|
282
|
+
schema: { message_id: z.number().optional() },
|
|
283
|
+
execute: (params, bridge) => bridge("unpin_message", params),
|
|
284
|
+
frontends: ["telegram"],
|
|
285
|
+
tag: "messaging",
|
|
286
|
+
},
|
|
287
|
+
|
|
288
|
+
// ── stop_poll ─────────────────────────────────────────────────────────
|
|
289
|
+
{
|
|
290
|
+
name: "stop_poll",
|
|
291
|
+
description:
|
|
292
|
+
"Stop an active poll and get the final results. Returns vote counts for each option.",
|
|
293
|
+
schema: {
|
|
294
|
+
message_id: z.number().describe("Message ID of the poll to stop"),
|
|
295
|
+
},
|
|
296
|
+
execute: (params, bridge) => bridge("stop_poll", params),
|
|
297
|
+
frontends: ["telegram"],
|
|
298
|
+
tag: "messaging",
|
|
299
|
+
},
|
|
300
|
+
];
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scheduling tools — cron CRUD and scheduled message cancellation.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import type { ToolDefinition } from "./types.js";
|
|
7
|
+
|
|
8
|
+
export const schedulingTools: ToolDefinition[] = [
|
|
9
|
+
{
|
|
10
|
+
name: "cancel_scheduled",
|
|
11
|
+
description: "Cancel a scheduled message.",
|
|
12
|
+
schema: { schedule_id: z.string() },
|
|
13
|
+
execute: (params, bridge) => bridge("cancel_scheduled", params),
|
|
14
|
+
frontends: ["telegram"],
|
|
15
|
+
tag: "scheduling",
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
{
|
|
19
|
+
name: "create_cron_job",
|
|
20
|
+
description: `Create a persistent recurring scheduled job. Jobs survive restarts.
|
|
21
|
+
|
|
22
|
+
Cron format: "minute hour day month weekday" (5 fields)
|
|
23
|
+
Examples:
|
|
24
|
+
"0 9 * * *" = every day at 9:00 AM
|
|
25
|
+
"30 14 * * 1-5" = weekdays at 2:30 PM
|
|
26
|
+
"*/15 * * * *" = every 15 minutes
|
|
27
|
+
"0 0 1 * *" = first day of every month at midnight
|
|
28
|
+
"0 8 * * 1" = every Monday at 8:00 AM
|
|
29
|
+
|
|
30
|
+
Type "message" sends the content as a text message.
|
|
31
|
+
Type "query" runs the content as a Claude prompt with full tool access (can search, create files, send messages, etc).`,
|
|
32
|
+
schema: {
|
|
33
|
+
name: z.string().describe("Human-readable name for the job"),
|
|
34
|
+
schedule: z
|
|
35
|
+
.string()
|
|
36
|
+
.describe("Cron expression (5-field: minute hour day month weekday)"),
|
|
37
|
+
type: z
|
|
38
|
+
.enum(["message", "query"])
|
|
39
|
+
.describe(
|
|
40
|
+
"Job type: 'message' sends text, 'query' runs a Claude prompt",
|
|
41
|
+
),
|
|
42
|
+
content: z.string().describe("Message text or query prompt"),
|
|
43
|
+
timezone: z
|
|
44
|
+
.string()
|
|
45
|
+
.optional()
|
|
46
|
+
.describe(
|
|
47
|
+
"IANA timezone (e.g. 'America/New_York'). Defaults to system timezone.",
|
|
48
|
+
),
|
|
49
|
+
},
|
|
50
|
+
execute: (params, bridge) => bridge("create_cron_job", params),
|
|
51
|
+
tag: "scheduling",
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
{
|
|
55
|
+
name: "list_cron_jobs",
|
|
56
|
+
description:
|
|
57
|
+
"List all cron jobs in the current chat with their status, schedule, run count, and next run time.",
|
|
58
|
+
schema: {},
|
|
59
|
+
execute: (_params, bridge) => bridge("list_cron_jobs", {}),
|
|
60
|
+
tag: "scheduling",
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
{
|
|
64
|
+
name: "edit_cron_job",
|
|
65
|
+
description:
|
|
66
|
+
"Edit an existing cron job. Only provide the fields you want to change.",
|
|
67
|
+
schema: {
|
|
68
|
+
job_id: z.string().describe("Job ID to edit"),
|
|
69
|
+
name: z.string().optional().describe("New name"),
|
|
70
|
+
schedule: z.string().optional().describe("New cron expression"),
|
|
71
|
+
type: z.enum(["message", "query"]).optional().describe("New job type"),
|
|
72
|
+
content: z.string().optional().describe("New content"),
|
|
73
|
+
enabled: z.boolean().optional().describe("Enable or disable the job"),
|
|
74
|
+
timezone: z.string().optional().describe("New IANA timezone"),
|
|
75
|
+
},
|
|
76
|
+
execute: (params, bridge) => bridge("edit_cron_job", params),
|
|
77
|
+
tag: "scheduling",
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
{
|
|
81
|
+
name: "delete_cron_job",
|
|
82
|
+
description: "Delete a cron job permanently.",
|
|
83
|
+
schema: {
|
|
84
|
+
job_id: z.string().describe("Job ID to delete"),
|
|
85
|
+
},
|
|
86
|
+
execute: (params, bridge) => bridge("delete_cron_job", params),
|
|
87
|
+
tag: "scheduling",
|
|
88
|
+
},
|
|
89
|
+
];
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sticker tools — browse, download, save, create, and manage sticker packs.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import type { ToolDefinition } from "./types.js";
|
|
7
|
+
|
|
8
|
+
export const stickerTools: ToolDefinition[] = [
|
|
9
|
+
{
|
|
10
|
+
name: "get_sticker_pack",
|
|
11
|
+
description:
|
|
12
|
+
"Get all stickers in a sticker pack by its name. Returns emoji + file_id for each sticker so you can send them. Use when you see a sticker set name in chat history.",
|
|
13
|
+
schema: {
|
|
14
|
+
set_name: z
|
|
15
|
+
.string()
|
|
16
|
+
.describe(
|
|
17
|
+
"Sticker set name (e.g. 'AnimatedEmojis' or from sticker metadata)",
|
|
18
|
+
),
|
|
19
|
+
},
|
|
20
|
+
execute: (params, bridge) => bridge("get_sticker_pack", params),
|
|
21
|
+
frontends: ["telegram"],
|
|
22
|
+
tag: "stickers",
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
{
|
|
26
|
+
name: "download_sticker",
|
|
27
|
+
description:
|
|
28
|
+
"Download a sticker image to workspace so you can view its contents. Returns the file path.",
|
|
29
|
+
schema: {
|
|
30
|
+
file_id: z
|
|
31
|
+
.string()
|
|
32
|
+
.describe("Sticker file_id from chat history or sticker pack listing"),
|
|
33
|
+
},
|
|
34
|
+
execute: (params, bridge) => bridge("download_sticker", params),
|
|
35
|
+
frontends: ["telegram"],
|
|
36
|
+
tag: "stickers",
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
{
|
|
40
|
+
name: "save_sticker_pack",
|
|
41
|
+
description:
|
|
42
|
+
"Save a sticker pack's file_ids to workspace for quick reuse. Once saved, you can read the JSON file to find stickers by emoji and send them instantly.",
|
|
43
|
+
schema: {
|
|
44
|
+
set_name: z.string().describe("Sticker set name"),
|
|
45
|
+
},
|
|
46
|
+
execute: (params, bridge) => bridge("save_sticker_pack", params),
|
|
47
|
+
frontends: ["telegram"],
|
|
48
|
+
tag: "stickers",
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
{
|
|
52
|
+
name: "create_sticker_set",
|
|
53
|
+
description: `Create a new sticker pack owned by a user. The bot will be the creator.
|
|
54
|
+
Sticker images must be PNG/WEBP, max 512x512px for static stickers.
|
|
55
|
+
The set name will automatically get "_by_<botname>" appended if needed.
|
|
56
|
+
|
|
57
|
+
Example: create_sticker_set(user_id=123, name="cool_pack", title="Cool Stickers", file_path="/path/to/sticker.png", emoji_list=["😎"])`,
|
|
58
|
+
schema: {
|
|
59
|
+
user_id: z.number().describe("Telegram user ID who will own the pack"),
|
|
60
|
+
name: z
|
|
61
|
+
.string()
|
|
62
|
+
.describe(
|
|
63
|
+
"Short name for the pack (a-z, 0-9, underscores). Will get _by_<botname> appended.",
|
|
64
|
+
),
|
|
65
|
+
title: z.string().describe("Display title for the pack (1-64 chars)"),
|
|
66
|
+
file_path: z
|
|
67
|
+
.string()
|
|
68
|
+
.describe("Path to the sticker image file (PNG/WEBP, 512x512 max)"),
|
|
69
|
+
emoji_list: z
|
|
70
|
+
.array(z.string())
|
|
71
|
+
.optional()
|
|
72
|
+
.describe("Emojis for this sticker (default: ['🎨'])"),
|
|
73
|
+
format: z
|
|
74
|
+
.enum(["static", "animated", "video"])
|
|
75
|
+
.optional()
|
|
76
|
+
.describe("Sticker format (default: static)"),
|
|
77
|
+
},
|
|
78
|
+
execute: (params, bridge) => bridge("create_sticker_set", params),
|
|
79
|
+
frontends: ["telegram"],
|
|
80
|
+
tag: "stickers",
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
{
|
|
84
|
+
name: "add_sticker_to_set",
|
|
85
|
+
description:
|
|
86
|
+
"Add a new sticker to an existing sticker pack created by the bot.",
|
|
87
|
+
schema: {
|
|
88
|
+
user_id: z.number().describe("Telegram user ID who owns the pack"),
|
|
89
|
+
name: z.string().describe("Sticker set name (including _by_<botname>)"),
|
|
90
|
+
file_path: z.string().describe("Path to the sticker image file"),
|
|
91
|
+
emoji_list: z
|
|
92
|
+
.array(z.string())
|
|
93
|
+
.optional()
|
|
94
|
+
.describe("Emojis for this sticker (default: ['🎨'])"),
|
|
95
|
+
format: z
|
|
96
|
+
.enum(["static", "animated", "video"])
|
|
97
|
+
.optional()
|
|
98
|
+
.describe("Sticker format (default: static)"),
|
|
99
|
+
},
|
|
100
|
+
execute: (params, bridge) => bridge("add_sticker_to_set", params),
|
|
101
|
+
frontends: ["telegram"],
|
|
102
|
+
tag: "stickers",
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
{
|
|
106
|
+
name: "delete_sticker_from_set",
|
|
107
|
+
description: "Remove a specific sticker from a pack by its file_id.",
|
|
108
|
+
schema: {
|
|
109
|
+
sticker_file_id: z
|
|
110
|
+
.string()
|
|
111
|
+
.describe(
|
|
112
|
+
"file_id of the sticker to remove (get from get_sticker_pack)",
|
|
113
|
+
),
|
|
114
|
+
},
|
|
115
|
+
execute: (params, bridge) => bridge("delete_sticker_from_set", params),
|
|
116
|
+
frontends: ["telegram"],
|
|
117
|
+
tag: "stickers",
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
{
|
|
121
|
+
name: "set_sticker_set_title",
|
|
122
|
+
description: "Change the title of a sticker pack created by the bot.",
|
|
123
|
+
schema: {
|
|
124
|
+
name: z.string().describe("Sticker set name"),
|
|
125
|
+
title: z.string().describe("New title (1-64 chars)"),
|
|
126
|
+
},
|
|
127
|
+
execute: (params, bridge) => bridge("set_sticker_set_title", params),
|
|
128
|
+
frontends: ["telegram"],
|
|
129
|
+
tag: "stickers",
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
{
|
|
133
|
+
name: "delete_sticker_set",
|
|
134
|
+
description:
|
|
135
|
+
"Permanently delete an entire sticker pack created by the bot.",
|
|
136
|
+
schema: {
|
|
137
|
+
name: z.string().describe("Sticker set name to delete"),
|
|
138
|
+
},
|
|
139
|
+
execute: (params, bridge) => bridge("delete_sticker_set", params),
|
|
140
|
+
frontends: ["telegram"],
|
|
141
|
+
tag: "stickers",
|
|
142
|
+
},
|
|
143
|
+
];
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for the modular tool system.
|
|
3
|
+
*
|
|
4
|
+
* Tool definitions are pure data + execute logic — no MCP imports,
|
|
5
|
+
* no bridge coupling. The MCP server consumes these via composeTools().
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ZodRawShape } from "zod";
|
|
9
|
+
|
|
10
|
+
/** Which frontends a tool is available on. "all" = every frontend. */
|
|
11
|
+
export type ToolFrontend = "telegram" | "teams" | "terminal" | "all";
|
|
12
|
+
|
|
13
|
+
/** Domain tags for runtime filtering and grouping. */
|
|
14
|
+
export type ToolTag =
|
|
15
|
+
| "messaging"
|
|
16
|
+
| "chat"
|
|
17
|
+
| "history"
|
|
18
|
+
| "members"
|
|
19
|
+
| "media"
|
|
20
|
+
| "stickers"
|
|
21
|
+
| "scheduling"
|
|
22
|
+
| "web";
|
|
23
|
+
|
|
24
|
+
/** The bridge caller signature — injected into execute(). */
|
|
25
|
+
export type BridgeFunction = (
|
|
26
|
+
action: string,
|
|
27
|
+
params: Record<string, unknown>,
|
|
28
|
+
) => Promise<unknown>;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* A self-contained tool definition.
|
|
32
|
+
*
|
|
33
|
+
* Contains everything needed to register it with an MCP server
|
|
34
|
+
* AND to know which bridge action it maps to.
|
|
35
|
+
*/
|
|
36
|
+
export interface ToolDefinition {
|
|
37
|
+
/** MCP tool name (e.g. "send", "react", "fetch_url"). */
|
|
38
|
+
readonly name: string;
|
|
39
|
+
|
|
40
|
+
/** Human-readable description shown to the model. */
|
|
41
|
+
readonly description: string;
|
|
42
|
+
|
|
43
|
+
/** Zod schema shape for the tool's input parameters. */
|
|
44
|
+
readonly schema: ZodRawShape;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Execute the tool. Receives validated params and a bridge caller.
|
|
48
|
+
* Returns the raw bridge result (wrapped by the MCP layer).
|
|
49
|
+
*/
|
|
50
|
+
readonly execute: (
|
|
51
|
+
params: Record<string, unknown>,
|
|
52
|
+
bridge: BridgeFunction,
|
|
53
|
+
) => Promise<unknown>;
|
|
54
|
+
|
|
55
|
+
/** Which frontends this tool appears on. Omit for all frontends. */
|
|
56
|
+
readonly frontends?: readonly ToolFrontend[];
|
|
57
|
+
|
|
58
|
+
/** Grouping tag. */
|
|
59
|
+
readonly tag: ToolTag;
|
|
60
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web tools — URL fetching.
|
|
3
|
+
*
|
|
4
|
+
* Platform-agnostic, available on all frontends.
|
|
5
|
+
* Web search is handled by the Brave Search MCP server (registered in
|
|
6
|
+
* src/backend/claude-sdk/index.ts) when configured. URL fetching is provided here via
|
|
7
|
+
* the `fetch_url` tool, so Claude Code's built-in WebSearch / WebFetch are
|
|
8
|
+
* disabled in favor of these project-specific replacements.
|
|
9
|
+
* These can be excluded via composeTools({ excludeTags: ["web"] }).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
import type { ToolDefinition } from "./types.js";
|
|
14
|
+
|
|
15
|
+
export const webTools: ToolDefinition[] = [
|
|
16
|
+
{
|
|
17
|
+
name: "fetch_url",
|
|
18
|
+
description:
|
|
19
|
+
"Fetch a URL — web pages return text content, image URLs are downloaded to workspace. Use to read articles, download images, or fetch any URL.",
|
|
20
|
+
schema: {
|
|
21
|
+
url: z.string().describe("The URL to fetch"),
|
|
22
|
+
},
|
|
23
|
+
execute: (params, bridge) => bridge("fetch_url", params),
|
|
24
|
+
tag: "web",
|
|
25
|
+
},
|
|
26
|
+
];
|
|
@@ -278,7 +278,10 @@ export function createTelegramActionHandler(
|
|
|
278
278
|
case "send_voice":
|
|
279
279
|
case "send_audio": {
|
|
280
280
|
const filePath = String(body.file_path ?? "");
|
|
281
|
-
const caption = body.caption
|
|
281
|
+
const caption = body.caption
|
|
282
|
+
? markdownToTelegramHtml(String(body.caption))
|
|
283
|
+
: undefined;
|
|
284
|
+
const captionParseMode = caption ? ("HTML" as const) : undefined;
|
|
282
285
|
gateway.incrementMessages(chatId);
|
|
283
286
|
if (action === "send_file") {
|
|
284
287
|
const stat = statSync(filePath);
|
|
@@ -294,6 +297,7 @@ export function createTelegramActionHandler(
|
|
|
294
297
|
sent = await withRetry(() =>
|
|
295
298
|
bot.api.sendDocument(chatId, file, {
|
|
296
299
|
caption,
|
|
300
|
+
parse_mode: captionParseMode,
|
|
297
301
|
reply_parameters: rp,
|
|
298
302
|
}),
|
|
299
303
|
);
|
|
@@ -302,6 +306,7 @@ export function createTelegramActionHandler(
|
|
|
302
306
|
sent = await withRetry(() =>
|
|
303
307
|
bot.api.sendPhoto(chatId, file, {
|
|
304
308
|
caption,
|
|
309
|
+
parse_mode: captionParseMode,
|
|
305
310
|
reply_parameters: rp,
|
|
306
311
|
}),
|
|
307
312
|
);
|
|
@@ -310,6 +315,7 @@ export function createTelegramActionHandler(
|
|
|
310
315
|
sent = await withRetry(() =>
|
|
311
316
|
bot.api.sendVideo(chatId, file, {
|
|
312
317
|
caption,
|
|
318
|
+
parse_mode: captionParseMode,
|
|
313
319
|
reply_parameters: rp,
|
|
314
320
|
}),
|
|
315
321
|
);
|
|
@@ -318,6 +324,7 @@ export function createTelegramActionHandler(
|
|
|
318
324
|
sent = await withRetry(() =>
|
|
319
325
|
bot.api.sendAnimation(chatId, file, {
|
|
320
326
|
caption,
|
|
327
|
+
parse_mode: captionParseMode,
|
|
321
328
|
reply_parameters: rp,
|
|
322
329
|
}),
|
|
323
330
|
);
|
|
@@ -326,6 +333,7 @@ export function createTelegramActionHandler(
|
|
|
326
333
|
sent = await withRetry(() =>
|
|
327
334
|
bot.api.sendAudio(chatId, file, {
|
|
328
335
|
caption,
|
|
336
|
+
parse_mode: captionParseMode,
|
|
329
337
|
reply_parameters: rp,
|
|
330
338
|
title: body.title as string | undefined,
|
|
331
339
|
performer: body.performer as string | undefined,
|
|
@@ -336,6 +344,7 @@ export function createTelegramActionHandler(
|
|
|
336
344
|
sent = await withRetry(() =>
|
|
337
345
|
bot.api.sendVoice(chatId, file, {
|
|
338
346
|
caption,
|
|
347
|
+
parse_mode: captionParseMode,
|
|
339
348
|
reply_parameters: rp,
|
|
340
349
|
}),
|
|
341
350
|
);
|