@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@projectservan8n/cnapse",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "description": "Autonomous PC intelligence - AI assistant for desktop automation",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -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
- '/run <cmd> - Execute command\n' +
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
- match = lower.match(/(?:focus|open|switch to)\s+(?:the\s+)?(.+)/i);
446
- if (match) {
447
- const result = await computer.focusWindow(match[1].trim());
448
- return result.success ? `✅ ${result.output}` : `❌ ${result.error}`;
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