talon-agent 1.2.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 +7 -6
- package/prompts/dream.md +6 -2
- package/prompts/mempalace.md +57 -0
- package/src/__tests__/compose-tools.test.ts +216 -0
- package/src/__tests__/cron-store-extended.test.ts +1 -1
- package/src/__tests__/dream.test.ts +118 -1
- package/src/__tests__/fuzz.test.ts +1 -3
- package/src/__tests__/gateway-actions.test.ts +1 -423
- package/src/__tests__/gateway-retry.test.ts +0 -4
- package/src/__tests__/handlers.test.ts +0 -4
- package/src/__tests__/heartbeat.test.ts +3 -0
- package/src/__tests__/mempalace-plugin.test.ts +295 -0
- package/src/__tests__/plugin.test.ts +169 -0
- package/src/__tests__/storage-save-errors.test.ts +1 -1
- package/src/__tests__/time.test.ts +1 -1
- package/src/__tests__/watchdog.test.ts +1 -3
- package/src/__tests__/workspace.test.ts +0 -1
- package/src/backend/claude-sdk/index.ts +39 -54
- package/src/backend/opencode/index.ts +5 -20
- package/src/bootstrap.ts +140 -11
- package/src/core/dream.ts +40 -6
- package/src/core/gateway-actions.ts +0 -87
- package/src/core/plugin.ts +103 -16
- 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/frontend/telegram/handlers.ts +5 -17
- package/src/plugins/github/index.ts +106 -0
- package/src/plugins/mempalace/index.ts +147 -0
- package/src/plugins/playwright/index.ts +82 -0
- package/src/storage/sessions.ts +0 -10
- package/src/util/config.ts +31 -1
- package/src/util/log.ts +4 -1
- package/src/util/paths.ts +9 -0
- package/src/backend/claude-sdk/tools.ts +0 -651
- package/src/frontend/teams/tools.ts +0 -175
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chat history tools — read, search, get messages, download media.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import type { ToolDefinition } from "./types.js";
|
|
7
|
+
|
|
8
|
+
export const historyTools: ToolDefinition[] = [
|
|
9
|
+
{
|
|
10
|
+
name: "read_chat_history",
|
|
11
|
+
description:
|
|
12
|
+
"Read messages from the chat. Use 'before' to go back in time (e.g. '2026-03-13').",
|
|
13
|
+
schema: {
|
|
14
|
+
limit: z
|
|
15
|
+
.number()
|
|
16
|
+
.optional()
|
|
17
|
+
.describe("Number of messages (default 30, max 100)"),
|
|
18
|
+
before: z
|
|
19
|
+
.string()
|
|
20
|
+
.optional()
|
|
21
|
+
.describe("Fetch messages before this date (ISO format)"),
|
|
22
|
+
offset_id: z.number().optional().describe("Fetch before this message ID"),
|
|
23
|
+
},
|
|
24
|
+
execute: (params, bridge) =>
|
|
25
|
+
bridge("read_history", {
|
|
26
|
+
limit: params.limit ?? 30,
|
|
27
|
+
before: params.before,
|
|
28
|
+
offset_id: params.offset_id,
|
|
29
|
+
}),
|
|
30
|
+
frontends: ["telegram"],
|
|
31
|
+
tag: "history",
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
{
|
|
35
|
+
name: "search_chat_history",
|
|
36
|
+
description: "Search messages by keyword.",
|
|
37
|
+
schema: {
|
|
38
|
+
query: z.string(),
|
|
39
|
+
limit: z.number().optional(),
|
|
40
|
+
},
|
|
41
|
+
execute: (params, bridge) => bridge("search_history", params),
|
|
42
|
+
frontends: ["telegram"],
|
|
43
|
+
tag: "history",
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
{
|
|
47
|
+
name: "get_user_messages",
|
|
48
|
+
description: "Get messages from a specific user.",
|
|
49
|
+
schema: {
|
|
50
|
+
user_name: z.string(),
|
|
51
|
+
limit: z.number().optional(),
|
|
52
|
+
},
|
|
53
|
+
execute: (params, bridge) => bridge("get_user_messages", params),
|
|
54
|
+
frontends: ["telegram"],
|
|
55
|
+
tag: "history",
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
{
|
|
59
|
+
name: "get_message_by_id",
|
|
60
|
+
description: "Get a specific message by ID.",
|
|
61
|
+
schema: { message_id: z.number() },
|
|
62
|
+
execute: (params, bridge) => bridge("get_message_by_id", params),
|
|
63
|
+
frontends: ["telegram"],
|
|
64
|
+
tag: "history",
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
{
|
|
68
|
+
name: "download_media",
|
|
69
|
+
description:
|
|
70
|
+
"Download a photo, document, or other media from a message by its ID. Saves the file to the workspace and returns the file path so you can read/analyze it. Use this when you see a [photo] or [document] in chat history but don't have the file.",
|
|
71
|
+
schema: {
|
|
72
|
+
message_id: z
|
|
73
|
+
.number()
|
|
74
|
+
.describe("Message ID containing the media to download"),
|
|
75
|
+
},
|
|
76
|
+
execute: (params, bridge) => bridge("download_media", params),
|
|
77
|
+
frontends: ["telegram"],
|
|
78
|
+
tag: "history",
|
|
79
|
+
},
|
|
80
|
+
];
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool registry — compose filtered tool sets at runtime.
|
|
3
|
+
*
|
|
4
|
+
* Import domain modules, expose a single composeTools() API
|
|
5
|
+
* that backends and the MCP server use to get the right tool set.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ToolDefinition, ToolFrontend, ToolTag } from "./types.js";
|
|
9
|
+
|
|
10
|
+
import { messagingTools } from "./messaging.js";
|
|
11
|
+
import { chatTools } from "./chat.js";
|
|
12
|
+
import { historyTools } from "./history.js";
|
|
13
|
+
import { memberTools } from "./members.js";
|
|
14
|
+
import { mediaTools } from "./media.js";
|
|
15
|
+
import { stickerTools } from "./stickers.js";
|
|
16
|
+
import { schedulingTools } from "./scheduling.js";
|
|
17
|
+
import { webTools } from "./web.js";
|
|
18
|
+
|
|
19
|
+
/** All built-in tool definitions. */
|
|
20
|
+
export const ALL_TOOLS: readonly ToolDefinition[] = [
|
|
21
|
+
...messagingTools,
|
|
22
|
+
...chatTools,
|
|
23
|
+
...historyTools,
|
|
24
|
+
...memberTools,
|
|
25
|
+
...mediaTools,
|
|
26
|
+
...stickerTools,
|
|
27
|
+
...schedulingTools,
|
|
28
|
+
...webTools,
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
/** Filter options for composing a tool set. */
|
|
32
|
+
export interface ComposeOptions {
|
|
33
|
+
/** Include only tools available on this frontend. */
|
|
34
|
+
frontend?: ToolFrontend;
|
|
35
|
+
/** Include only tools with these tags. */
|
|
36
|
+
tags?: ToolTag[];
|
|
37
|
+
/** Exclude tools with these tags. */
|
|
38
|
+
excludeTags?: ToolTag[];
|
|
39
|
+
/** Exclude specific tools by name. */
|
|
40
|
+
excludeNames?: string[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Compose a filtered set of tools at runtime.
|
|
45
|
+
*
|
|
46
|
+
* When no options are provided, returns ALL_TOOLS unchanged.
|
|
47
|
+
* Callers describe what they need and get back matching definitions.
|
|
48
|
+
*/
|
|
49
|
+
export function composeTools(options: ComposeOptions = {}): ToolDefinition[] {
|
|
50
|
+
let tools = [...ALL_TOOLS];
|
|
51
|
+
|
|
52
|
+
if (options.frontend) {
|
|
53
|
+
tools = tools.filter(
|
|
54
|
+
(t) =>
|
|
55
|
+
!t.frontends ||
|
|
56
|
+
t.frontends.includes("all") ||
|
|
57
|
+
t.frontends.includes(options.frontend!),
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (options.tags?.length) {
|
|
62
|
+
tools = tools.filter((t) => options.tags!.includes(t.tag));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (options.excludeTags?.length) {
|
|
66
|
+
tools = tools.filter((t) => !options.excludeTags!.includes(t.tag));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (options.excludeNames?.length) {
|
|
70
|
+
tools = tools.filter((t) => !options.excludeNames!.includes(t.name));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return tools;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Re-export types for convenience
|
|
77
|
+
export type {
|
|
78
|
+
ToolDefinition,
|
|
79
|
+
ToolFrontend,
|
|
80
|
+
ToolTag,
|
|
81
|
+
BridgeFunction,
|
|
82
|
+
} from "./types.js";
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Unified MCP server — replaces per-backend tools.ts files.
|
|
4
|
+
*
|
|
5
|
+
* Reads TALON_FRONTEND env var to compose the right tool set,
|
|
6
|
+
* then registers them all on a single MCP server over stdio.
|
|
7
|
+
*
|
|
8
|
+
* Environment:
|
|
9
|
+
* TALON_BRIDGE_URL — HTTP bridge URL (default: http://127.0.0.1:19876)
|
|
10
|
+
* TALON_CHAT_ID — Current chat ID
|
|
11
|
+
* TALON_FRONTEND — Frontend type: "telegram" | "teams" | "terminal" (default: "telegram")
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
15
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
16
|
+
import { composeTools } from "./index.js";
|
|
17
|
+
import { createBridge, textResult } from "./bridge.js";
|
|
18
|
+
import type { ToolFrontend } from "./types.js";
|
|
19
|
+
|
|
20
|
+
const VALID_FRONTENDS = new Set<ToolFrontend>([
|
|
21
|
+
"telegram",
|
|
22
|
+
"teams",
|
|
23
|
+
"terminal",
|
|
24
|
+
]);
|
|
25
|
+
const BRIDGE_URL = process.env.TALON_BRIDGE_URL || "http://127.0.0.1:19876";
|
|
26
|
+
const CHAT_ID = process.env.TALON_CHAT_ID || "";
|
|
27
|
+
const rawFrontend = (process.env.TALON_FRONTEND || "telegram") as ToolFrontend;
|
|
28
|
+
|
|
29
|
+
if (!CHAT_ID) {
|
|
30
|
+
console.warn(
|
|
31
|
+
"TALON_CHAT_ID is not set — bridge calls will fail without a valid chat context.",
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!VALID_FRONTENDS.has(rawFrontend)) {
|
|
36
|
+
console.error(
|
|
37
|
+
`Invalid TALON_FRONTEND: "${rawFrontend}". Must be one of: ${[...VALID_FRONTENDS].join(", ")}`,
|
|
38
|
+
);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
const FRONTEND = rawFrontend;
|
|
42
|
+
|
|
43
|
+
const bridge = createBridge(BRIDGE_URL, CHAT_ID);
|
|
44
|
+
const serverName = `${FRONTEND}-tools`;
|
|
45
|
+
const server = new McpServer({ name: serverName, version: "3.0.0" });
|
|
46
|
+
|
|
47
|
+
// Compose and register all tools for the active frontend
|
|
48
|
+
const tools = composeTools({ frontend: FRONTEND });
|
|
49
|
+
|
|
50
|
+
for (const tool of tools) {
|
|
51
|
+
server.tool(tool.name, tool.description, tool.schema, async (params) =>
|
|
52
|
+
textResult(await tool.execute(params, bridge)),
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function main() {
|
|
57
|
+
const transport = new StdioServerTransport();
|
|
58
|
+
await server.connect(transport);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
main().catch((err) => {
|
|
62
|
+
console.error("MCP server error:", err);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Media tools — list recent media in a chat.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import type { ToolDefinition } from "./types.js";
|
|
7
|
+
|
|
8
|
+
export const mediaTools: ToolDefinition[] = [
|
|
9
|
+
{
|
|
10
|
+
name: "list_media",
|
|
11
|
+
description:
|
|
12
|
+
"List recent photos, documents, and other media in the current chat with file paths. Use this to find a previously sent photo or file to re-read or reference.",
|
|
13
|
+
schema: {
|
|
14
|
+
limit: z
|
|
15
|
+
.number()
|
|
16
|
+
.optional()
|
|
17
|
+
.describe("Number of entries (default 10, max 20)"),
|
|
18
|
+
},
|
|
19
|
+
execute: (params, bridge) => bridge("list_media", { limit: params.limit }),
|
|
20
|
+
frontends: ["telegram"],
|
|
21
|
+
tag: "media",
|
|
22
|
+
},
|
|
23
|
+
];
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Member tools — list members, user info, online count, pinned messages.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import type { ToolDefinition } from "./types.js";
|
|
7
|
+
|
|
8
|
+
export const memberTools: ToolDefinition[] = [
|
|
9
|
+
{
|
|
10
|
+
name: "list_chat_members",
|
|
11
|
+
description: "List chat members with names, IDs, online status, badges.",
|
|
12
|
+
schema: { limit: z.number().optional() },
|
|
13
|
+
execute: (params, bridge) =>
|
|
14
|
+
bridge("list_known_users", { limit: params.limit }),
|
|
15
|
+
frontends: ["telegram"],
|
|
16
|
+
tag: "members",
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
{
|
|
20
|
+
name: "get_member_info",
|
|
21
|
+
description: "Get detailed info about a user by ID.",
|
|
22
|
+
schema: { user_id: z.number() },
|
|
23
|
+
execute: (params, bridge) => bridge("get_member_info", params),
|
|
24
|
+
frontends: ["telegram"],
|
|
25
|
+
tag: "members",
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
{
|
|
29
|
+
name: "online_count",
|
|
30
|
+
description:
|
|
31
|
+
"Get how many members are currently online or recently active.",
|
|
32
|
+
schema: {},
|
|
33
|
+
execute: (_params, bridge) => bridge("online_count", {}),
|
|
34
|
+
frontends: ["telegram"],
|
|
35
|
+
tag: "members",
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
{
|
|
39
|
+
name: "get_pinned_messages",
|
|
40
|
+
description: "Get all pinned messages in the current chat.",
|
|
41
|
+
schema: {},
|
|
42
|
+
execute: (_params, bridge) => bridge("get_pinned_messages", {}),
|
|
43
|
+
frontends: ["telegram"],
|
|
44
|
+
tag: "members",
|
|
45
|
+
},
|
|
46
|
+
];
|
|
@@ -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
|
+
];
|