@projectservan8n/cnapse 0.8.0 → 0.8.1
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/dist/index.js +915 -845
- package/package.json +1 -1
- package/src/services/telegram.ts +101 -6
package/package.json
CHANGED
package/src/services/telegram.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { describeScreen, captureScreenshot } from '../lib/vision.js';
|
|
|
8
8
|
import { runCommand } from '../tools/shell.js';
|
|
9
9
|
import { chat as chatWithAI, chatWithVision, Message } from '../lib/api.js';
|
|
10
10
|
import * as computer from '../tools/computer.js';
|
|
11
|
+
import { parseTask, executeTask, formatTask, Task, TaskStep } from '../lib/tasks.js';
|
|
11
12
|
|
|
12
13
|
export interface TelegramMessage {
|
|
13
14
|
chatId: number;
|
|
@@ -199,8 +200,14 @@ export class TelegramBotService extends EventEmitter {
|
|
|
199
200
|
'Commands:\n' +
|
|
200
201
|
'/screen - Take screenshot\n' +
|
|
201
202
|
'/describe - Screenshot + AI description\n' +
|
|
202
|
-
'/
|
|
203
|
+
'/task <desc> - Multi-step automation\n' +
|
|
204
|
+
'/run <cmd> - Execute shell command\n' +
|
|
203
205
|
'/status - System status\n\n' +
|
|
206
|
+
'Examples:\n' +
|
|
207
|
+
'• /task open folder E:/Test and list files\n' +
|
|
208
|
+
'• /task open notepad and type hello\n' +
|
|
209
|
+
'• minimize chrome\n' +
|
|
210
|
+
'• what windows are open?\n\n' +
|
|
204
211
|
`Your chat ID: ${chatId}`
|
|
205
212
|
);
|
|
206
213
|
});
|
|
@@ -303,6 +310,90 @@ export class TelegramBotService extends EventEmitter {
|
|
|
303
310
|
await ctx.reply(status);
|
|
304
311
|
});
|
|
305
312
|
|
|
313
|
+
// /task command - multi-step task automation
|
|
314
|
+
this.bot.command('task', async (ctx: any) => {
|
|
315
|
+
if (!this.isAllowed(ctx.chat.id)) {
|
|
316
|
+
await ctx.reply('⛔ Not authorized. Send /start first.');
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const taskDescription = ctx.message.text.replace('/task', '').trim();
|
|
321
|
+
if (!taskDescription) {
|
|
322
|
+
await ctx.reply(
|
|
323
|
+
'📋 Usage: /task <description>\n\n' +
|
|
324
|
+
'Examples:\n' +
|
|
325
|
+
'• /task open notepad and type hello\n' +
|
|
326
|
+
'• /task open folder E:/Test and list files\n' +
|
|
327
|
+
'• /task search google for weather today\n' +
|
|
328
|
+
'• /task open chrome and go to github.com'
|
|
329
|
+
);
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
await ctx.reply(`🎯 Parsing task: "${taskDescription}"`);
|
|
334
|
+
|
|
335
|
+
try {
|
|
336
|
+
// Parse the task into steps
|
|
337
|
+
const task = await parseTask(taskDescription);
|
|
338
|
+
|
|
339
|
+
// Show the parsed steps
|
|
340
|
+
let stepsPreview = `📋 Task broken into ${task.steps.length} steps:\n\n`;
|
|
341
|
+
task.steps.forEach((step, i) => {
|
|
342
|
+
stepsPreview += `${i + 1}. ${step.description}\n`;
|
|
343
|
+
});
|
|
344
|
+
stepsPreview += '\n⏳ Executing...';
|
|
345
|
+
await ctx.reply(stepsPreview);
|
|
346
|
+
|
|
347
|
+
// Execute the task with progress updates
|
|
348
|
+
let lastUpdate = Date.now();
|
|
349
|
+
const updatedTask = await executeTask(task, async (t: Task, step: TaskStep) => {
|
|
350
|
+
// Send progress update (throttled to avoid spam)
|
|
351
|
+
const now = Date.now();
|
|
352
|
+
if (now - lastUpdate > 2000) { // Update every 2 seconds max
|
|
353
|
+
lastUpdate = now;
|
|
354
|
+
const stepNum = t.steps.indexOf(step) + 1;
|
|
355
|
+
const status = step.status === 'running' ? '🔄' : step.status === 'completed' ? '✅' : '❌';
|
|
356
|
+
await ctx.sendChatAction('typing');
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
// Send final result
|
|
361
|
+
const result = formatTask(updatedTask);
|
|
362
|
+
|
|
363
|
+
// Split if too long
|
|
364
|
+
if (result.length > 4000) {
|
|
365
|
+
const chunks = result.match(/.{1,4000}/gs) || [result];
|
|
366
|
+
for (const chunk of chunks) {
|
|
367
|
+
await sendFormattedMessage(ctx, chunk);
|
|
368
|
+
}
|
|
369
|
+
} else {
|
|
370
|
+
await sendFormattedMessage(ctx, result);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// If task had vision/screenshot steps, send a final screenshot
|
|
374
|
+
const hasScreenStep = updatedTask.steps.some(s =>
|
|
375
|
+
s.action.includes('screenshot') || s.action.includes('describe')
|
|
376
|
+
);
|
|
377
|
+
if (hasScreenStep || updatedTask.status === 'completed') {
|
|
378
|
+
try {
|
|
379
|
+
const screenshot = await captureScreenshot();
|
|
380
|
+
if (screenshot) {
|
|
381
|
+
const buffer = Buffer.from(screenshot, 'base64');
|
|
382
|
+
await ctx.replyWithPhoto({ source: buffer }, {
|
|
383
|
+
caption: updatedTask.status === 'completed'
|
|
384
|
+
? '✅ Task completed - current screen'
|
|
385
|
+
: '📸 Final screen state'
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
} catch {
|
|
389
|
+
// Screenshot failed, that's ok
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
} catch (error) {
|
|
393
|
+
await ctx.reply(`❌ Task failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
|
|
306
397
|
// Handle text messages - forward to AI and respond
|
|
307
398
|
this.bot.on('text', async (ctx: any) => {
|
|
308
399
|
if (!this.isAllowed(ctx.chat.id)) {
|
|
@@ -441,11 +532,15 @@ export class TelegramBotService extends EventEmitter {
|
|
|
441
532
|
return result.success ? `✅ ${result.output}` : `❌ ${result.error}`;
|
|
442
533
|
}
|
|
443
534
|
|
|
444
|
-
// Focus/open window
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
535
|
+
// Focus/open window - but NOT if it looks like a multi-step task
|
|
536
|
+
// Skip if text contains task indicators: "and", "then", "tell me", "list", "what", etc.
|
|
537
|
+
const looksLikeTask = /\b(and|then|after|tell me|list|what|show|describe|check|find|search|create|write|type\s+.+\s+in)\b/i.test(text);
|
|
538
|
+
if (!looksLikeTask) {
|
|
539
|
+
match = lower.match(/(?:focus|open|switch to)\s+(?:the\s+)?(\w+(?:\s+\w+)?)/i);
|
|
540
|
+
if (match) {
|
|
541
|
+
const result = await computer.focusWindow(match[1].trim());
|
|
542
|
+
return result.success ? `✅ ${result.output}` : `❌ ${result.error}`;
|
|
543
|
+
}
|
|
449
544
|
}
|
|
450
545
|
|
|
451
546
|
// Type text
|