@projectservan8n/cnapse 0.4.0 → 0.5.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/ConfigUI-V5TM6KKS.js +306 -0
- package/dist/index.js +1031 -377
- package/package.json +1 -1
- package/src/components/App.tsx +172 -291
- package/src/components/ConfigUI.tsx +353 -0
- package/src/components/HelpMenu.tsx +2 -1
- package/src/components/ProviderSelector.tsx +378 -0
- package/src/hooks/index.ts +15 -0
- package/src/hooks/useChat.ts +149 -0
- package/src/hooks/useTasks.ts +63 -0
- package/src/hooks/useTelegram.ts +91 -0
- package/src/hooks/useVision.ts +47 -0
- package/src/index.tsx +95 -83
- package/src/lib/ollama.ts +140 -0
- package/src/lib/tasks.ts +249 -34
package/src/lib/tasks.ts
CHANGED
|
@@ -1,14 +1,120 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Task Automation - Multi-step task sequencing
|
|
3
3
|
* Parses natural language into actionable steps and executes them
|
|
4
|
+
* Uses chain-of-thought prompting + learning from past tasks
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
import { chat, Message } from './api.js';
|
|
7
8
|
import * as computer from '../tools/computer.js';
|
|
8
9
|
import { describeScreen } from './vision.js';
|
|
10
|
+
import * as fs from 'fs';
|
|
11
|
+
import * as path from 'path';
|
|
12
|
+
import * as os from 'os';
|
|
9
13
|
|
|
10
14
|
export type TaskStepStatus = 'pending' | 'running' | 'completed' | 'failed' | 'skipped';
|
|
11
15
|
|
|
16
|
+
// Task memory file location
|
|
17
|
+
const TASK_MEMORY_FILE = path.join(os.homedir(), '.cnapse', 'task-memory.json');
|
|
18
|
+
|
|
19
|
+
interface TaskPattern {
|
|
20
|
+
input: string;
|
|
21
|
+
normalizedInput: string;
|
|
22
|
+
steps: Array<{ description: string; action: string }>;
|
|
23
|
+
successCount: number;
|
|
24
|
+
lastUsed: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface TaskMemory {
|
|
28
|
+
patterns: TaskPattern[];
|
|
29
|
+
version: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Load learned task patterns from disk
|
|
34
|
+
*/
|
|
35
|
+
function loadTaskMemory(): TaskMemory {
|
|
36
|
+
try {
|
|
37
|
+
if (fs.existsSync(TASK_MEMORY_FILE)) {
|
|
38
|
+
const data = fs.readFileSync(TASK_MEMORY_FILE, 'utf-8');
|
|
39
|
+
return JSON.parse(data);
|
|
40
|
+
}
|
|
41
|
+
} catch {
|
|
42
|
+
// Ignore errors, return empty memory
|
|
43
|
+
}
|
|
44
|
+
return { patterns: [], version: 1 };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Save task pattern to memory
|
|
49
|
+
*/
|
|
50
|
+
function saveTaskPattern(input: string, steps: Array<{ description: string; action: string }>): void {
|
|
51
|
+
try {
|
|
52
|
+
const memory = loadTaskMemory();
|
|
53
|
+
const normalized = normalizeInput(input);
|
|
54
|
+
|
|
55
|
+
// Find existing pattern or create new
|
|
56
|
+
const existing = memory.patterns.find(p => p.normalizedInput === normalized);
|
|
57
|
+
if (existing) {
|
|
58
|
+
existing.steps = steps;
|
|
59
|
+
existing.successCount++;
|
|
60
|
+
existing.lastUsed = new Date().toISOString();
|
|
61
|
+
} else {
|
|
62
|
+
memory.patterns.push({
|
|
63
|
+
input,
|
|
64
|
+
normalizedInput: normalized,
|
|
65
|
+
steps,
|
|
66
|
+
successCount: 1,
|
|
67
|
+
lastUsed: new Date().toISOString(),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Keep only last 100 patterns
|
|
72
|
+
memory.patterns = memory.patterns
|
|
73
|
+
.sort((a, b) => b.successCount - a.successCount)
|
|
74
|
+
.slice(0, 100);
|
|
75
|
+
|
|
76
|
+
// Ensure directory exists
|
|
77
|
+
const dir = path.dirname(TASK_MEMORY_FILE);
|
|
78
|
+
if (!fs.existsSync(dir)) {
|
|
79
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
fs.writeFileSync(TASK_MEMORY_FILE, JSON.stringify(memory, null, 2));
|
|
83
|
+
} catch {
|
|
84
|
+
// Ignore write errors
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Normalize input for pattern matching
|
|
90
|
+
*/
|
|
91
|
+
function normalizeInput(input: string): string {
|
|
92
|
+
return input
|
|
93
|
+
.toLowerCase()
|
|
94
|
+
.replace(/[^\w\s]/g, ' ')
|
|
95
|
+
.replace(/\s+/g, ' ')
|
|
96
|
+
.trim();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Find similar learned patterns
|
|
101
|
+
*/
|
|
102
|
+
function findSimilarPatterns(input: string): TaskPattern[] {
|
|
103
|
+
const memory = loadTaskMemory();
|
|
104
|
+
const normalized = normalizeInput(input);
|
|
105
|
+
const words = normalized.split(' ').filter(w => w.length > 2);
|
|
106
|
+
|
|
107
|
+
return memory.patterns
|
|
108
|
+
.filter(pattern => {
|
|
109
|
+
// Check if patterns share key action words
|
|
110
|
+
const patternWords = pattern.normalizedInput.split(' ');
|
|
111
|
+
const matches = words.filter(w => patternWords.includes(w));
|
|
112
|
+
return matches.length >= Math.min(2, words.length * 0.5);
|
|
113
|
+
})
|
|
114
|
+
.sort((a, b) => b.successCount - a.successCount)
|
|
115
|
+
.slice(0, 3);
|
|
116
|
+
}
|
|
117
|
+
|
|
12
118
|
export interface TaskStep {
|
|
13
119
|
id: string;
|
|
14
120
|
description: string;
|
|
@@ -30,49 +136,120 @@ export interface Task {
|
|
|
30
136
|
export type TaskProgressCallback = (task: Task, step: TaskStep) => void;
|
|
31
137
|
|
|
32
138
|
/**
|
|
33
|
-
*
|
|
139
|
+
* Build chain-of-thought prompt for task parsing
|
|
140
|
+
* This guides small models through systematic reasoning
|
|
34
141
|
*/
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
142
|
+
function buildChainOfThoughtPrompt(input: string): string {
|
|
143
|
+
// Find similar patterns the model has successfully executed before
|
|
144
|
+
const similarPatterns = findSimilarPatterns(input);
|
|
145
|
+
|
|
146
|
+
let learnedExamples = '';
|
|
147
|
+
if (similarPatterns.length > 0) {
|
|
148
|
+
learnedExamples = `
|
|
149
|
+
## LEARNED PATTERNS (from successful past tasks)
|
|
150
|
+
These patterns worked before - use them as reference:
|
|
151
|
+
|
|
152
|
+
${similarPatterns.map((p, i) => `
|
|
153
|
+
Pattern ${i + 1} (used ${p.successCount} times):
|
|
154
|
+
Input: "${p.input}"
|
|
155
|
+
Steps: ${JSON.stringify(p.steps, null, 2)}
|
|
156
|
+
`).join('\n')}
|
|
157
|
+
`;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return `You are a task parser for Windows PC automation. Your job is to convert natural language into precise, executable steps.
|
|
161
|
+
|
|
162
|
+
## THINKING PROCESS
|
|
163
|
+
Before outputting steps, THINK through these questions:
|
|
164
|
+
|
|
165
|
+
1. **WHAT** is the main goal?
|
|
166
|
+
- What application needs to open?
|
|
167
|
+
- What action needs to happen inside it?
|
|
168
|
+
- What is the expected end result?
|
|
169
|
+
|
|
170
|
+
2. **HOW** to achieve it on Windows?
|
|
171
|
+
- Use Win+R (meta+r) to open Run dialog for apps
|
|
172
|
+
- Wait 1-3 seconds after opening apps for them to load
|
|
173
|
+
- Use keyboard shortcuts when possible (faster, more reliable)
|
|
174
|
+
- Common shortcuts: Ctrl+S (save), Ctrl+O (open), Ctrl+N (new), Alt+F4 (close)
|
|
175
|
+
|
|
176
|
+
3. **SEQUENCE** - what order makes sense?
|
|
177
|
+
- Open app FIRST
|
|
178
|
+
- WAIT for it to load
|
|
179
|
+
- THEN interact with it
|
|
180
|
+
- Add waits between actions that need time
|
|
181
|
+
|
|
182
|
+
4. **EDGE CASES** - what could go wrong?
|
|
183
|
+
- App might already be open -> focus_window first
|
|
184
|
+
- Dialogs might appear -> handle or dismiss them
|
|
185
|
+
- Typing too fast -> add small waits
|
|
186
|
+
|
|
187
|
+
## AVAILABLE ACTIONS
|
|
188
|
+
- open_app: Open app via Run dialog (e.g., "open_app:notepad", "open_app:code", "open_app:chrome")
|
|
189
|
+
- type_text: Type text string (e.g., "type_text:Hello World")
|
|
190
|
+
- press_key: Single key (e.g., "press_key:enter", "press_key:escape", "press_key:tab")
|
|
191
|
+
- key_combo: Key combination (e.g., "key_combo:control+s", "key_combo:alt+f4", "key_combo:meta+r")
|
|
192
|
+
- click: Mouse click (e.g., "click:left", "click:right")
|
|
193
|
+
- wait: Wait N seconds (e.g., "wait:2" - use 1-3s for app loads)
|
|
194
|
+
- focus_window: Focus by title (e.g., "focus_window:Notepad")
|
|
195
|
+
- screenshot: Capture and describe screen
|
|
196
|
+
${learnedExamples}
|
|
197
|
+
## EXAMPLES WITH REASONING
|
|
198
|
+
|
|
199
|
+
### Example 1: "open notepad and type hello"
|
|
200
|
+
Thinking:
|
|
201
|
+
- Goal: Open Notepad, then type text into it
|
|
202
|
+
- How: Win+R -> notepad -> Enter to open, then type
|
|
203
|
+
- Sequence: Open -> Wait for load -> Type
|
|
204
|
+
- Edge case: Need wait time for Notepad window to be ready
|
|
205
|
+
|
|
206
|
+
Output:
|
|
49
207
|
[
|
|
50
|
-
{ "description": "
|
|
51
|
-
|
|
208
|
+
{ "description": "Open Notepad via Run dialog", "action": "open_app:notepad" },
|
|
209
|
+
{ "description": "Wait for Notepad to fully load", "action": "wait:2" },
|
|
210
|
+
{ "description": "Type the greeting text", "action": "type_text:hello" }
|
|
211
|
+
]
|
|
212
|
+
|
|
213
|
+
### Example 2: "save the current document"
|
|
214
|
+
Thinking:
|
|
215
|
+
- Goal: Save whatever is in the current app
|
|
216
|
+
- How: Ctrl+S is universal save shortcut
|
|
217
|
+
- Sequence: Just the key combo, maybe wait for save
|
|
218
|
+
- Edge case: If file is new, Save As dialog might appear
|
|
219
|
+
|
|
220
|
+
Output:
|
|
221
|
+
[
|
|
222
|
+
{ "description": "Press Ctrl+S to save", "action": "key_combo:control+s" },
|
|
223
|
+
{ "description": "Wait for save to complete", "action": "wait:1" }
|
|
52
224
|
]
|
|
53
225
|
|
|
54
|
-
Example
|
|
55
|
-
|
|
226
|
+
### Example 3: "close this window"
|
|
227
|
+
Thinking:
|
|
228
|
+
- Goal: Close the current active window
|
|
229
|
+
- How: Alt+F4 closes active window on Windows
|
|
230
|
+
- Sequence: Single action
|
|
231
|
+
- Edge case: Might prompt to save - user handles that
|
|
232
|
+
|
|
233
|
+
Output:
|
|
56
234
|
[
|
|
57
|
-
{ "description": "
|
|
58
|
-
{ "description": "Wait for Notepad to open", "action": "wait:2" },
|
|
59
|
-
{ "description": "Type hello world", "action": "type_text:Hello World" }
|
|
235
|
+
{ "description": "Close active window with Alt+F4", "action": "key_combo:alt+f4" }
|
|
60
236
|
]
|
|
61
237
|
|
|
62
|
-
|
|
63
|
-
|
|
238
|
+
## YOUR TASK
|
|
239
|
+
Now parse this request: "${input}"
|
|
240
|
+
|
|
241
|
+
First, briefly think through the 4 questions above, then output ONLY a JSON array:
|
|
64
242
|
[
|
|
65
|
-
{ "description": "
|
|
66
|
-
|
|
67
|
-
{ "description": "Open folder with Ctrl+K Ctrl+O", "action": "key_combo:control+k" },
|
|
68
|
-
{ "description": "Wait for dialog", "action": "wait:1" },
|
|
69
|
-
{ "description": "Continue folder open", "action": "key_combo:control+o" },
|
|
70
|
-
{ "description": "Wait for folder dialog", "action": "wait:1" },
|
|
71
|
-
{ "description": "Type folder path", "action": "type_text:E:\\\\Projects" },
|
|
72
|
-
{ "description": "Press Enter to open folder", "action": "press_key:enter" },
|
|
73
|
-
{ "description": "Wait for folder to load", "action": "wait:2" },
|
|
74
|
-
{ "description": "Open terminal with Ctrl+\`", "action": "key_combo:control+\`" }
|
|
243
|
+
{ "description": "Human readable step", "action": "action_type:params" },
|
|
244
|
+
...
|
|
75
245
|
]`;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Parse natural language task into executable steps
|
|
250
|
+
*/
|
|
251
|
+
export async function parseTask(input: string): Promise<Task> {
|
|
252
|
+
const systemPrompt = buildChainOfThoughtPrompt(input);
|
|
76
253
|
|
|
77
254
|
const messages: Message[] = [
|
|
78
255
|
{ role: 'user', content: input }
|
|
@@ -219,6 +396,13 @@ export async function executeTask(
|
|
|
219
396
|
|
|
220
397
|
if (task.status !== 'failed') {
|
|
221
398
|
task.status = 'completed';
|
|
399
|
+
|
|
400
|
+
// Learn from successful tasks - save pattern for future use
|
|
401
|
+
const steps = task.steps.map(s => ({
|
|
402
|
+
description: s.description,
|
|
403
|
+
action: s.action,
|
|
404
|
+
}));
|
|
405
|
+
saveTaskPattern(task.description, steps);
|
|
222
406
|
}
|
|
223
407
|
task.completedAt = new Date();
|
|
224
408
|
|
|
@@ -232,6 +416,37 @@ function sleep(ms: number): Promise<void> {
|
|
|
232
416
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
233
417
|
}
|
|
234
418
|
|
|
419
|
+
/**
|
|
420
|
+
* Get task memory statistics
|
|
421
|
+
*/
|
|
422
|
+
export function getTaskMemoryStats(): { patternCount: number; totalUses: number; topPatterns: string[] } {
|
|
423
|
+
const memory = loadTaskMemory();
|
|
424
|
+
const totalUses = memory.patterns.reduce((sum, p) => sum + p.successCount, 0);
|
|
425
|
+
const topPatterns = memory.patterns
|
|
426
|
+
.sort((a, b) => b.successCount - a.successCount)
|
|
427
|
+
.slice(0, 5)
|
|
428
|
+
.map(p => `"${p.input}" (${p.successCount}x)`);
|
|
429
|
+
|
|
430
|
+
return {
|
|
431
|
+
patternCount: memory.patterns.length,
|
|
432
|
+
totalUses,
|
|
433
|
+
topPatterns,
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Clear task memory
|
|
439
|
+
*/
|
|
440
|
+
export function clearTaskMemory(): void {
|
|
441
|
+
try {
|
|
442
|
+
if (fs.existsSync(TASK_MEMORY_FILE)) {
|
|
443
|
+
fs.unlinkSync(TASK_MEMORY_FILE);
|
|
444
|
+
}
|
|
445
|
+
} catch {
|
|
446
|
+
// Ignore errors
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
235
450
|
/**
|
|
236
451
|
* Format task for display
|
|
237
452
|
*/
|