@sarkar-ai/deskmate 0.2.1 → 0.3.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.
@@ -1,333 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.startTelegramBot = startTelegramBot;
37
- const path = __importStar(require("path"));
38
- const grammy_1 = require("grammy");
39
- const executor_1 = require("../core/executor");
40
- const approval_1 = require("../core/approval");
41
- const agent_1 = require("../core/agent");
42
- const logger_1 = require("../core/logger");
43
- const platform_1 = require("../core/platform");
44
- const session_1 = require("../gateway/session");
45
- const log = (0, logger_1.createLogger)("TelegramBot");
46
- const BOT_NAME = process.env.BOT_NAME || "Deskmate";
47
- const SCREENSHOT_DIR = process.env.TMPDIR ? `${process.env.TMPDIR}deskmate-screenshots` : "/tmp/deskmate-screenshots";
48
- const SYSTEM_PROMPT = `You are a local machine assistant named ${BOT_NAME}. Users will ask you to perform tasks on their computer via Telegram.
49
-
50
- You have access to tools to execute commands, read/write files, and explore the filesystem. Use them to help users accomplish their tasks.
51
-
52
- SCREENSHOT CAPABILITY:
53
- When the user asks to see the screen, take a screenshot, or wants visual feedback, use this command:
54
- ${(0, platform_1.getScreenshotHint)(SCREENSHOT_DIR)}
55
- The screenshot will automatically be sent to the user via Telegram after your response.
56
-
57
- IMPORTANT RULES:
58
- - Be concise in your responses (Telegram messages should be brief)
59
- - Use the available tools to accomplish tasks
60
- - For dangerous operations, explain what you're about to do before doing it
61
- - Never use sudo unless explicitly asked
62
- - Keep responses under 4000 characters (Telegram limit)
63
- - When asked for screenshots, always use the screenshot command above`;
64
- async function startTelegramBot() {
65
- const token = process.env.TELEGRAM_BOT_TOKEN;
66
- const allowedUserId = parseInt(process.env.ALLOWED_USER_ID || "0", 10);
67
- if (!token || !allowedUserId) {
68
- throw new Error("TELEGRAM_BOT_TOKEN and ALLOWED_USER_ID required for Telegram bot");
69
- }
70
- const bot = new grammy_1.Bot(token);
71
- const executor = new executor_1.Executor();
72
- const workingDir = process.env.WORKING_DIR || process.env.HOME || "/";
73
- // Create the agent provider (abstracted - can be swapped)
74
- const agentProvider = (0, agent_1.createAgentProvider)();
75
- log.info("Using agent provider", { name: agentProvider.name, version: agentProvider.version });
76
- // Verify provider is available
77
- const providerAvailable = await agentProvider.isAvailable();
78
- if (!providerAvailable) {
79
- log.warn("Agent provider may not be fully available", { provider: agentProvider.name });
80
- }
81
- // Store session IDs per chat for context/memory (persisted to disk)
82
- const chatSessions = new session_1.SessionManager({
83
- storagePath: path.join(workingDir, "data", "sessions-telegram.json"),
84
- });
85
- // Register Telegram as an approval notifier
86
- approval_1.approvalManager.addNotifier(async (action) => {
87
- // This will be called when approval is needed
88
- // We send a Telegram message for the user to approve/reject
89
- try {
90
- const keyboard = new grammy_1.InlineKeyboard()
91
- .text("✅ Approve", `approve:${action.id}`)
92
- .text("❌ Reject", `reject:${action.id}`);
93
- let emoji = "🔐";
94
- let details = "";
95
- switch (action.type) {
96
- case "command":
97
- emoji = "⚡";
98
- details = `\`\`\`bash\n${action.details.command}\n\`\`\``;
99
- break;
100
- case "write_file":
101
- emoji = "📝";
102
- details = `Path: \`${action.details.path}\`\nPreview: ${(action.details.contentPreview || "").slice(0, 100)}...`;
103
- break;
104
- case "folder_access":
105
- emoji = "📁";
106
- details = `Folder: \`${action.details.baseFolder}\`\nFile: \`${action.details.path}\``;
107
- break;
108
- case "read_file":
109
- emoji = "👁️";
110
- details = `Path: \`${action.details.path}\``;
111
- break;
112
- default:
113
- details = JSON.stringify(action.details, null, 2);
114
- }
115
- const timeLeft = Math.ceil((action.expiresAt.getTime() - Date.now()) / 1000);
116
- await bot.api.sendMessage(allowedUserId, `${emoji} *Approval Required*\n\n` +
117
- `Type: \`${action.type}\`\n` +
118
- `${action.description}\n\n` +
119
- `${details}\n\n` +
120
- `⏱️ Expires in ${timeLeft}s`, { parse_mode: "Markdown", reply_markup: keyboard });
121
- log.info("Approval notification sent to Telegram", { actionId: action.id, type: action.type });
122
- }
123
- catch (error) {
124
- log.error("Failed to send Telegram notification", { error: error.message });
125
- }
126
- });
127
- // Auth middleware
128
- bot.use(async (ctx, next) => {
129
- if (ctx.from?.id !== allowedUserId) {
130
- console.log(`Unauthorized: ${ctx.from?.id}`);
131
- return;
132
- }
133
- await next();
134
- });
135
- // Commands
136
- bot.command("start", (ctx) => ctx.reply(`👋 *${BOT_NAME} Ready*\n\n` +
137
- "Send me any task and I'll execute it on your local machine.\n\n" +
138
- "I remember our conversation, so you can ask follow-up questions!\n\n" +
139
- "*Examples:*\n" +
140
- "• `list all docker containers`\n" +
141
- "• `what's using port 3000?`\n" +
142
- "• `show disk usage`\n" +
143
- "• `take a screenshot`\n\n" +
144
- "*Commands:*\n" +
145
- "• /screenshot - Take a screenshot\n" +
146
- "• /status - System info\n" +
147
- "• /reset - Clear memory & start fresh", { parse_mode: "Markdown" }));
148
- // Quick screenshot command
149
- bot.command("screenshot", async (ctx) => {
150
- const chatId = ctx.chat.id;
151
- await ctx.reply("📸 Taking screenshot...");
152
- try {
153
- const { exec } = await Promise.resolve().then(() => __importStar(require("child_process")));
154
- const { promisify } = await Promise.resolve().then(() => __importStar(require("util")));
155
- const fs = await Promise.resolve().then(() => __importStar(require("fs/promises")));
156
- const path = await Promise.resolve().then(() => __importStar(require("path")));
157
- const execAsync = promisify(exec);
158
- await fs.mkdir(SCREENSHOT_DIR, { recursive: true });
159
- const filename = `screenshot-${Date.now()}.png`;
160
- const filepath = path.join(SCREENSHOT_DIR, filename);
161
- await execAsync((0, platform_1.getScreenshotCommand)(filepath));
162
- await bot.api.sendPhoto(chatId, new grammy_1.InputFile(filepath), {
163
- caption: "📸 Screenshot",
164
- });
165
- await fs.unlink(filepath).catch(() => { });
166
- log.info("Screenshot command completed", { chatId });
167
- }
168
- catch (error) {
169
- log.error("Screenshot command failed", { error: error.message });
170
- await ctx.reply(`❌ Screenshot failed: ${error.message}`);
171
- }
172
- });
173
- bot.command("status", async (ctx) => {
174
- const pending = approval_1.approvalManager.getPendingActions();
175
- const info = await executor.getSystemInfo();
176
- const hasSession = chatSessions.has("telegram", String(ctx.chat.id));
177
- await ctx.reply(`🖥️ *System Status*\n\n` +
178
- `• Host: ${info.hostname || "unknown"}\n` +
179
- `• Platform: ${info.platform}\n` +
180
- `• Agent: ${agentProvider.name} v${agentProvider.version}\n` +
181
- `• Pending approvals: ${pending.length}\n` +
182
- `• Working dir: \`${executor.getWorkingDir()}\`\n` +
183
- `• Session active: ${hasSession ? "Yes ✅" : "No"}`, { parse_mode: "Markdown" });
184
- });
185
- // Reset session - start fresh conversation
186
- bot.command("reset", async (ctx) => {
187
- const chatId = ctx.chat.id;
188
- const hadSession = chatSessions.has("telegram", String(chatId));
189
- chatSessions.delete("telegram", String(chatId));
190
- log.info("Session reset", { chatId, hadSession });
191
- await ctx.reply(hadSession
192
- ? "🔄 Session cleared! Starting fresh conversation."
193
- : "ℹ️ No active session to clear.");
194
- });
195
- // Approval callbacks
196
- bot.callbackQuery(/^approve:(.+)$/, async (ctx) => {
197
- const actionId = ctx.match[1];
198
- const success = approval_1.approvalManager.approve(actionId);
199
- await ctx.answerCallbackQuery({ text: success ? "Approved!" : "Action not found" });
200
- if (success) {
201
- await ctx.editMessageText("✅ *Approved* - executing...", { parse_mode: "Markdown" });
202
- }
203
- });
204
- bot.callbackQuery(/^reject:(.+)$/, async (ctx) => {
205
- const actionId = ctx.match[1];
206
- approval_1.approvalManager.reject(actionId);
207
- await ctx.answerCallbackQuery({ text: "Rejected" });
208
- await ctx.editMessageText("❌ *Rejected*", { parse_mode: "Markdown" });
209
- });
210
- // Helper to send screenshots
211
- async function sendScreenshots(chatId, since) {
212
- try {
213
- const fs = await Promise.resolve().then(() => __importStar(require("fs/promises")));
214
- const path = await Promise.resolve().then(() => __importStar(require("path")));
215
- await fs.mkdir(SCREENSHOT_DIR, { recursive: true }).catch(() => { });
216
- const files = await fs.readdir(SCREENSHOT_DIR).catch(() => []);
217
- let sent = 0;
218
- for (const file of files) {
219
- if (!file.endsWith(".png"))
220
- continue;
221
- const filepath = path.join(SCREENSHOT_DIR, file);
222
- const stats = await fs.stat(filepath).catch(() => null);
223
- if (stats && stats.mtime >= since) {
224
- log.info("Sending screenshot", { filepath });
225
- await bot.api.sendPhoto(chatId, new grammy_1.InputFile(filepath), {
226
- caption: "📸 Screenshot",
227
- });
228
- // Clean up after sending
229
- await fs.unlink(filepath).catch(() => { });
230
- sent++;
231
- }
232
- }
233
- return sent;
234
- }
235
- catch (error) {
236
- log.error("Failed to send screenshots", { error: error.message });
237
- return 0;
238
- }
239
- }
240
- // Main message handler - uses abstract agent provider
241
- bot.on("message:text", async (ctx) => {
242
- const userMessage = ctx.message.text;
243
- const chatId = ctx.chat.id;
244
- const thinkingMsg = await ctx.reply("🤔 Thinking...");
245
- // Record time before execution for screenshot detection
246
- const executionStartTime = new Date();
247
- // Get existing session for this chat (if any)
248
- const existingSessionId = chatSessions.get("telegram", String(chatId));
249
- log.info("Received message", {
250
- userId: ctx.from?.id,
251
- chatId,
252
- hasSession: !!existingSessionId,
253
- message: userMessage.slice(0, 100),
254
- });
255
- try {
256
- let result = "";
257
- let lastUpdate = Date.now();
258
- let newSessionId;
259
- // Use the abstract agent provider
260
- for await (const event of agentProvider.queryStream(userMessage, {
261
- systemPrompt: SYSTEM_PROMPT,
262
- workingDir,
263
- sessionId: existingSessionId,
264
- maxTurns: 10,
265
- })) {
266
- log.debug("Agent event", { type: event.type });
267
- switch (event.type) {
268
- case "text":
269
- if (event.text) {
270
- result = event.text;
271
- }
272
- break;
273
- case "tool_use":
274
- log.debug("Tool use", { tool: event.toolName });
275
- break;
276
- case "done":
277
- if (event.response) {
278
- result = event.response.text;
279
- newSessionId = event.response.sessionId;
280
- }
281
- break;
282
- case "error":
283
- throw new Error(event.error || "Unknown agent error");
284
- }
285
- // Update thinking message periodically to show progress
286
- if (Date.now() - lastUpdate > 3000) {
287
- try {
288
- await ctx.api.editMessageText(chatId, thinkingMsg.message_id, "⏳ Working...");
289
- lastUpdate = Date.now();
290
- }
291
- catch {
292
- // Ignore edit errors
293
- }
294
- }
295
- }
296
- // Store the session ID for future messages
297
- if (newSessionId) {
298
- chatSessions.set("telegram", String(chatId), newSessionId);
299
- log.info("Session stored", { chatId, sessionId: newSessionId });
300
- }
301
- // Send final result
302
- const finalMessage = result || "Task completed (no output)";
303
- const truncated = finalMessage.slice(0, 4000); // Telegram limit
304
- log.info("Agent completed", { resultLength: finalMessage.length, hasSession: !!newSessionId });
305
- await ctx.api.editMessageText(chatId, thinkingMsg.message_id, truncated, {
306
- parse_mode: "Markdown",
307
- }).catch(async () => {
308
- // If Markdown fails, try without
309
- await ctx.api.editMessageText(chatId, thinkingMsg.message_id, truncated);
310
- });
311
- // Check for and send any screenshots taken during execution
312
- const screenshotsSent = await sendScreenshots(chatId, executionStartTime);
313
- if (screenshotsSent > 0) {
314
- log.info("Screenshots sent", { count: screenshotsSent });
315
- }
316
- }
317
- catch (error) {
318
- log.error("Agent error", { error: error.message });
319
- // If session error, clear the session and retry might help
320
- if (error.message?.includes("session")) {
321
- chatSessions.delete("telegram", String(chatId));
322
- log.warn("Cleared invalid session", { chatId });
323
- }
324
- await ctx.api.editMessageText(chatId, thinkingMsg.message_id, `❌ Error: ${error.message}`);
325
- }
326
- });
327
- // Start polling
328
- console.log("🤖 Starting Telegram bot...");
329
- bot.start({
330
- drop_pending_updates: true, // Don't process old updates on restart
331
- onStart: (info) => console.log(`✅ Telegram bot @${info.username} running (${agentProvider.name})`),
332
- });
333
- }