@projectservan8n/cnapse 0.8.2 → 0.10.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.
@@ -0,0 +1,366 @@
1
+ import {
2
+ chat
3
+ } from "./chunk-GP73OJCZ.js";
4
+ import {
5
+ pressKey,
6
+ typeText
7
+ } from "./chunk-TFHK5CYF.js";
8
+ import {
9
+ describeScreen
10
+ } from "./chunk-OIVTPXE4.js";
11
+
12
+ // src/agents/learner.ts
13
+ import { EventEmitter } from "events";
14
+ import { homedir } from "os";
15
+ import { join } from "path";
16
+ import { readFile, writeFile, mkdir } from "fs/promises";
17
+ import { existsSync } from "fs";
18
+ var MEMORY_FILE = join(homedir(), ".cnapse", "agent-memory.json");
19
+ var MAX_MEMORY_SIZE = 500;
20
+ var AgentLearner = class extends EventEmitter {
21
+ memory;
22
+ loaded = false;
23
+ constructor() {
24
+ super();
25
+ this.memory = this.createEmptyMemory();
26
+ }
27
+ createEmptyMemory() {
28
+ return {
29
+ version: 1,
30
+ learned: [],
31
+ stats: {
32
+ totalAttempts: 0,
33
+ totalSuccesses: 0,
34
+ totalLearned: 0,
35
+ sourceCounts: {}
36
+ }
37
+ };
38
+ }
39
+ /**
40
+ * Load memory from disk
41
+ */
42
+ async load() {
43
+ if (this.loaded) return;
44
+ try {
45
+ const dir = join(homedir(), ".cnapse");
46
+ if (!existsSync(dir)) {
47
+ await mkdir(dir, { recursive: true });
48
+ }
49
+ if (existsSync(MEMORY_FILE)) {
50
+ const data = await readFile(MEMORY_FILE, "utf-8");
51
+ this.memory = JSON.parse(data);
52
+ }
53
+ this.loaded = true;
54
+ this.emit("loaded", this.memory.learned.length);
55
+ } catch (error) {
56
+ console.error("Failed to load agent memory:", error);
57
+ this.memory = this.createEmptyMemory();
58
+ this.loaded = true;
59
+ }
60
+ }
61
+ /**
62
+ * Save memory to disk
63
+ */
64
+ async save() {
65
+ try {
66
+ const dir = join(homedir(), ".cnapse");
67
+ if (!existsSync(dir)) {
68
+ await mkdir(dir, { recursive: true });
69
+ }
70
+ if (this.memory.learned.length > MAX_MEMORY_SIZE) {
71
+ this.memory.learned.sort((a, b) => {
72
+ const aScore = a.successCount - a.failCount;
73
+ const bScore = b.successCount - b.failCount;
74
+ return bScore - aScore;
75
+ });
76
+ this.memory.learned = this.memory.learned.slice(0, MAX_MEMORY_SIZE);
77
+ }
78
+ await writeFile(MEMORY_FILE, JSON.stringify(this.memory, null, 2));
79
+ this.emit("saved", this.memory.learned.length);
80
+ } catch (error) {
81
+ console.error("Failed to save agent memory:", error);
82
+ }
83
+ }
84
+ /**
85
+ * Check if we've solved something similar before
86
+ */
87
+ async recall(goal, currentScreen) {
88
+ await this.load();
89
+ const goalLower = goal.toLowerCase();
90
+ const screenLower = currentScreen.toLowerCase();
91
+ const candidates = this.memory.learned.filter((m) => {
92
+ const goalSimilarity = this.calculateSimilarity(goalLower, m.goal.toLowerCase());
93
+ const screenSimilarity = this.calculateSimilarity(screenLower, m.situation.toLowerCase());
94
+ return goalSimilarity > 0.5 && screenSimilarity > 0.3;
95
+ });
96
+ if (candidates.length === 0) return null;
97
+ candidates.sort((a, b) => {
98
+ const aScore = a.successCount - a.failCount + a.successCount * 0.1;
99
+ const bScore = b.successCount - b.failCount + b.successCount * 0.1;
100
+ return bScore - aScore;
101
+ });
102
+ const best = candidates[0];
103
+ if (best.successCount > best.failCount) {
104
+ this.emit("recalled", best);
105
+ return best;
106
+ }
107
+ return null;
108
+ }
109
+ /**
110
+ * Simple word-based similarity (Jaccard-like)
111
+ */
112
+ calculateSimilarity(a, b) {
113
+ const wordsA = new Set(a.split(/\s+/).filter((w) => w.length > 2));
114
+ const wordsB = new Set(b.split(/\s+/).filter((w) => w.length > 2));
115
+ if (wordsA.size === 0 || wordsB.size === 0) return 0;
116
+ let intersection = 0;
117
+ for (const word of wordsA) {
118
+ if (wordsB.has(word)) intersection++;
119
+ }
120
+ const union = wordsA.size + wordsB.size - intersection;
121
+ return intersection / union;
122
+ }
123
+ /**
124
+ * Learn from a successful action
125
+ */
126
+ async learn(situation, goal, actionType, actionValue, source) {
127
+ await this.load();
128
+ const id = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
129
+ const now = (/* @__PURE__ */ new Date()).toISOString();
130
+ const existing = this.memory.learned.find(
131
+ (m) => m.goal.toLowerCase() === goal.toLowerCase() && m.actionType === actionType && m.actionValue === actionValue
132
+ );
133
+ if (existing) {
134
+ existing.successCount++;
135
+ existing.lastUsed = now;
136
+ this.emit("reinforced", existing);
137
+ } else {
138
+ const learned = {
139
+ id,
140
+ situation: situation.slice(0, 500),
141
+ // Truncate long descriptions
142
+ goal,
143
+ solution: `${actionType}: ${actionValue}`,
144
+ actionType,
145
+ actionValue,
146
+ source,
147
+ successCount: 1,
148
+ failCount: 0,
149
+ lastUsed: now,
150
+ created: now
151
+ };
152
+ this.memory.learned.push(learned);
153
+ this.memory.stats.totalLearned++;
154
+ this.memory.stats.sourceCounts[source] = (this.memory.stats.sourceCounts[source] || 0) + 1;
155
+ this.emit("learned", learned);
156
+ }
157
+ await this.save();
158
+ }
159
+ /**
160
+ * Record a failed attempt
161
+ */
162
+ async recordFailure(goal, actionType, actionValue) {
163
+ await this.load();
164
+ const existing = this.memory.learned.find(
165
+ (m) => m.goal.toLowerCase() === goal.toLowerCase() && m.actionType === actionType && m.actionValue === actionValue
166
+ );
167
+ if (existing) {
168
+ existing.failCount++;
169
+ await this.save();
170
+ }
171
+ }
172
+ /**
173
+ * Get help from multiple AI sources when stuck
174
+ */
175
+ async getHelp(goal, currentScreen, triedActions) {
176
+ this.emit("seeking_help", { goal, triedActions: triedActions.length });
177
+ const query = this.buildHelpQuery(goal, currentScreen, triedActions);
178
+ const suggestions = [];
179
+ const sources = await Promise.allSettled([
180
+ this.askOwnAI(query),
181
+ this.askPerplexity(goal, currentScreen),
182
+ this.askWebSearch(goal)
183
+ ]);
184
+ for (const result of sources) {
185
+ if (result.status === "fulfilled" && result.value) {
186
+ suggestions.push(result.value);
187
+ }
188
+ }
189
+ suggestions.sort((a, b) => b.confidence - a.confidence);
190
+ this.emit("got_help", suggestions.length);
191
+ return suggestions;
192
+ }
193
+ buildHelpQuery(goal, screen, tried) {
194
+ return `I'm trying to: ${goal}
195
+
196
+ Current screen shows: ${screen.slice(0, 500)}
197
+
198
+ Actions I've already tried:
199
+ ${tried.length > 0 ? tried.slice(-5).join("\n") : "None yet"}
200
+
201
+ What's the SINGLE next action I should take?
202
+ Be very specific - tell me exactly what to click, what to type, or what key to press.
203
+
204
+ Respond in this format:
205
+ ACTION: <click|type|press|navigate|scroll|wait>
206
+ VALUE: <what to click on / text to type / key to press / URL to navigate to>
207
+ REASONING: <brief explanation>`;
208
+ }
209
+ /**
210
+ * Ask our configured AI provider for help
211
+ */
212
+ async askOwnAI(query) {
213
+ try {
214
+ const response = await chat([{ role: "user", content: query }]);
215
+ return this.parseSuggestion(response.content, "own_ai", 0.8);
216
+ } catch (error) {
217
+ return null;
218
+ }
219
+ }
220
+ /**
221
+ * Ask Perplexity by opening browser (uses existing browser.ts)
222
+ */
223
+ async askPerplexity(goal, screen) {
224
+ try {
225
+ const browser = await import("./browser-YLFWQXIY.js");
226
+ const question = `How do I ${goal}? I can see ${screen.slice(0, 200)}. Give me the exact next step.`;
227
+ const result = await browser.askAI("perplexity", question);
228
+ if (result.response) {
229
+ return {
230
+ action: "suggested",
231
+ value: result.response.slice(0, 500),
232
+ reasoning: "From Perplexity web search",
233
+ source: "perplexity",
234
+ confidence: 0.7
235
+ };
236
+ }
237
+ } catch (error) {
238
+ }
239
+ return null;
240
+ }
241
+ /**
242
+ * Search Google for help
243
+ */
244
+ async askWebSearch(goal) {
245
+ try {
246
+ const browser = await import("./browser-YLFWQXIY.js");
247
+ const searchQuery = `how to ${goal} step by step`;
248
+ const result = await browser.webSearch(searchQuery);
249
+ if (result.description) {
250
+ return {
251
+ action: "suggested",
252
+ value: result.description.slice(0, 500),
253
+ reasoning: "From web search results",
254
+ source: "google",
255
+ confidence: 0.5
256
+ };
257
+ }
258
+ } catch (error) {
259
+ }
260
+ return null;
261
+ }
262
+ /**
263
+ * Parse AI response into structured suggestion
264
+ */
265
+ parseSuggestion(content, source, baseConfidence) {
266
+ const actionMatch = content.match(/ACTION:\s*(\w+)/i);
267
+ const valueMatch = content.match(/VALUE:\s*(.+?)(?:\n|$)/i);
268
+ const reasoningMatch = content.match(/REASONING:\s*(.+?)(?:\n|$)/i);
269
+ if (!actionMatch) return null;
270
+ return {
271
+ action: actionMatch[1].toLowerCase(),
272
+ value: valueMatch?.[1]?.trim() || "",
273
+ reasoning: reasoningMatch?.[1]?.trim() || "No reasoning provided",
274
+ source,
275
+ confidence: baseConfidence
276
+ };
277
+ }
278
+ /**
279
+ * Ask Claude.ai via browser automation
280
+ */
281
+ async askClaude(query) {
282
+ try {
283
+ const browser = await import("./browser-YLFWQXIY.js");
284
+ await browser.openUrl("https://claude.ai");
285
+ await this.sleep(3e3);
286
+ await typeText(query);
287
+ await this.sleep(500);
288
+ await pressKey("Return");
289
+ await this.sleep(8e3);
290
+ const screen = await describeScreen();
291
+ return {
292
+ action: "suggested",
293
+ value: screen.description.slice(0, 500),
294
+ reasoning: "From Claude.ai",
295
+ source: "claude_web",
296
+ confidence: 0.75
297
+ };
298
+ } catch (error) {
299
+ return null;
300
+ }
301
+ }
302
+ /**
303
+ * Ask ChatGPT via browser automation
304
+ */
305
+ async askChatGPT(query) {
306
+ try {
307
+ const browser = await import("./browser-YLFWQXIY.js");
308
+ await browser.openUrl("https://chat.openai.com");
309
+ await this.sleep(3e3);
310
+ await typeText(query);
311
+ await this.sleep(500);
312
+ await pressKey("Return");
313
+ await this.sleep(8e3);
314
+ const screen = await describeScreen();
315
+ return {
316
+ action: "suggested",
317
+ value: screen.description.slice(0, 500),
318
+ reasoning: "From ChatGPT",
319
+ source: "chatgpt_web",
320
+ confidence: 0.75
321
+ };
322
+ } catch (error) {
323
+ return null;
324
+ }
325
+ }
326
+ sleep(ms) {
327
+ return new Promise((resolve) => setTimeout(resolve, ms));
328
+ }
329
+ /**
330
+ * Get memory stats
331
+ */
332
+ getStats() {
333
+ return {
334
+ ...this.memory.stats,
335
+ memorySize: this.memory.learned.length
336
+ };
337
+ }
338
+ /**
339
+ * Get all learned actions (for debugging/display)
340
+ */
341
+ getAllLearned() {
342
+ return [...this.memory.learned];
343
+ }
344
+ /**
345
+ * Clear all memory (for testing)
346
+ */
347
+ async clearMemory() {
348
+ this.memory = this.createEmptyMemory();
349
+ await this.save();
350
+ this.emit("cleared");
351
+ }
352
+ };
353
+ var learnerInstance = null;
354
+ function getLearner() {
355
+ if (!learnerInstance) {
356
+ learnerInstance = new AgentLearner();
357
+ }
358
+ return learnerInstance;
359
+ }
360
+ var learner_default = AgentLearner;
361
+
362
+ export {
363
+ AgentLearner,
364
+ getLearner,
365
+ learner_default
366
+ };