prepia 1.0.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.
- package/LICENSE +21 -0
- package/README.md +312 -0
- package/bin/prepia.mjs +119 -0
- package/package.json +53 -0
- package/skill/SKILL.md +148 -0
- package/skill/config.json +29 -0
- package/src/analytics/dashboard.mjs +84 -0
- package/src/analytics/tracker.mjs +131 -0
- package/src/api/middleware.mjs +219 -0
- package/src/api/routes.mjs +142 -0
- package/src/api/server.mjs +150 -0
- package/src/cache/disk-store.mjs +199 -0
- package/src/cache/manager.mjs +142 -0
- package/src/cache/memory-store.mjs +205 -0
- package/src/chain/dag.mjs +209 -0
- package/src/chain/executor.mjs +103 -0
- package/src/chain/scheduler.mjs +89 -0
- package/src/client/adapters.mjs +483 -0
- package/src/client/connector.mjs +391 -0
- package/src/client/index.mjs +483 -0
- package/src/client/websocket.mjs +353 -0
- package/src/core/context-packager.mjs +169 -0
- package/src/core/engine.mjs +338 -0
- package/src/core/event-bus.mjs +84 -0
- package/src/core/prepimshot.mjs +120 -0
- package/src/core/task-decomposer.mjs +158 -0
- package/src/edge/lite.mjs +90 -0
- package/src/guard/checker.mjs +123 -0
- package/src/guard/fact-checker.mjs +105 -0
- package/src/guard/hallucination.mjs +108 -0
- package/src/index.mjs +67 -0
- package/src/models/local-model.mjs +171 -0
- package/src/models/provider.mjs +192 -0
- package/src/models/router.mjs +156 -0
- package/src/morph/optimizer.mjs +142 -0
- package/src/network/p2p.mjs +146 -0
- package/src/persona/detector.mjs +118 -0
- package/src/plugins/loader.mjs +120 -0
- package/src/plugins/registry.mjs +164 -0
- package/src/plugins/sandbox.mjs +79 -0
- package/src/rate/limiter.mjs +145 -0
- package/src/rate/shield.mjs +150 -0
- package/src/script/executor.mjs +164 -0
- package/src/script/parser.mjs +134 -0
- package/src/security/privacy.mjs +108 -0
- package/src/security/sanitizer.mjs +133 -0
- package/src/shadow/daemon.mjs +128 -0
- package/src/stream/handler.mjs +204 -0
- package/src/tools/calculator.mjs +312 -0
- package/src/tools/file-ops.mjs +138 -0
- package/src/tools/http-client.mjs +127 -0
- package/src/tools/orchestrator.mjs +205 -0
- package/src/tools/web-scraper.mjs +159 -0
- package/src/tools/web-search.mjs +129 -0
- package/src/vault/knowledge-base.mjs +207 -0
- package/src/vault/pattern-learner.mjs +192 -0
- package/workflows/analyze.json +32 -0
- package/workflows/automate.json +32 -0
- package/workflows/research.json +37 -0
- package/workflows/summarize.json +32 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Web search using Node 22 global fetch.
|
|
3
|
+
* @module tools/web-search
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {Object} SearchResult
|
|
8
|
+
* @property {string} title - Result title
|
|
9
|
+
* @property {string} url - Result URL
|
|
10
|
+
* @property {string} snippet - Result snippet/description
|
|
11
|
+
* @property {string} source - Search engine used
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Search the web using DuckDuckGo HTML (no API key needed).
|
|
16
|
+
* @param {string} query - Search query
|
|
17
|
+
* @param {Object} [options]
|
|
18
|
+
* @param {number} [options.maxResults=10] - Max results to return
|
|
19
|
+
* @param {string} [options.engine='duckduckgo'] - Search engine
|
|
20
|
+
* @returns {Promise<SearchResult[]>}
|
|
21
|
+
*/
|
|
22
|
+
export async function search(query, options = {}) {
|
|
23
|
+
if (!query || typeof query !== 'string') {
|
|
24
|
+
throw new Error('Query must be a non-empty string');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const { maxResults = 10, engine = 'duckduckgo' } = options;
|
|
28
|
+
|
|
29
|
+
switch (engine) {
|
|
30
|
+
case 'duckduckgo':
|
|
31
|
+
return searchDuckDuckGo(query, maxResults);
|
|
32
|
+
case 'wikipedia':
|
|
33
|
+
return searchWikipedia(query, maxResults);
|
|
34
|
+
default:
|
|
35
|
+
return searchDuckDuckGo(query, maxResults);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Search using DuckDuckGo HTML version.
|
|
41
|
+
* @param {string} query
|
|
42
|
+
* @param {number} maxResults
|
|
43
|
+
* @returns {Promise<SearchResult[]>}
|
|
44
|
+
*/
|
|
45
|
+
async function searchDuckDuckGo(query, maxResults) {
|
|
46
|
+
const url = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`;
|
|
47
|
+
try {
|
|
48
|
+
const response = await fetch(url, {
|
|
49
|
+
headers: {
|
|
50
|
+
'User-Agent': 'Mozilla/5.0 (compatible; Prepia/1.0)',
|
|
51
|
+
},
|
|
52
|
+
signal: AbortSignal.timeout(15000),
|
|
53
|
+
});
|
|
54
|
+
const html = await response.text();
|
|
55
|
+
return parseDuckDuckGoResults(html, maxResults);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
throw new Error(`DuckDuckGo search failed: ${err.message}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Parse DuckDuckGo HTML results.
|
|
63
|
+
* @param {string} html
|
|
64
|
+
* @param {number} maxResults
|
|
65
|
+
* @returns {SearchResult[]}
|
|
66
|
+
*/
|
|
67
|
+
function parseDuckDuckGoResults(html, maxResults) {
|
|
68
|
+
const results = [];
|
|
69
|
+
// Match result blocks
|
|
70
|
+
const resultPattern = /<a[^>]+class="result__a"[^>]*href="([^"]*)"[^>]*>(.*?)<\/a>/gs;
|
|
71
|
+
const snippetPattern = /<a[^>]+class="result__snippet"[^>]*>(.*?)<\/a>/gs;
|
|
72
|
+
|
|
73
|
+
const links = [];
|
|
74
|
+
let match;
|
|
75
|
+
|
|
76
|
+
// Extract links
|
|
77
|
+
while ((match = resultPattern.exec(html)) !== null && links.length < maxResults) {
|
|
78
|
+
let url = match[1];
|
|
79
|
+
// DuckDuckGo uses redirect URLs
|
|
80
|
+
const uddgMatch = url.match(/uddg=([^&]+)/);
|
|
81
|
+
if (uddgMatch) url = decodeURIComponent(uddgMatch[1]);
|
|
82
|
+
const title = match[2].replace(/<[^>]*>/g, '').trim();
|
|
83
|
+
if (title && url) links.push({ title, url });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Extract snippets
|
|
87
|
+
const snippets = [];
|
|
88
|
+
while ((match = snippetPattern.exec(html)) !== null) {
|
|
89
|
+
snippets.push(match[1].replace(/<[^>]*>/g, '').trim());
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
for (let i = 0; i < links.length && i < maxResults; i++) {
|
|
93
|
+
results.push({
|
|
94
|
+
title: links[i].title,
|
|
95
|
+
url: links[i].url,
|
|
96
|
+
snippet: snippets[i] || '',
|
|
97
|
+
source: 'duckduckgo',
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return results;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Search Wikipedia API.
|
|
106
|
+
* @param {string} query
|
|
107
|
+
* @param {number} maxResults
|
|
108
|
+
* @returns {Promise<SearchResult[]>}
|
|
109
|
+
*/
|
|
110
|
+
async function searchWikipedia(query, maxResults) {
|
|
111
|
+
const url = `https://en.wikipedia.org/w/api.php?action=query&list=search&srsearch=${encodeURIComponent(query)}&srlimit=${maxResults}&format=json`;
|
|
112
|
+
try {
|
|
113
|
+
const response = await fetch(url, {
|
|
114
|
+
headers: { 'User-Agent': 'Prepia/1.0' },
|
|
115
|
+
signal: AbortSignal.timeout(15000),
|
|
116
|
+
});
|
|
117
|
+
const data = await response.json();
|
|
118
|
+
return (data.query?.search || []).map(item => ({
|
|
119
|
+
title: item.title,
|
|
120
|
+
url: `https://en.wikipedia.org/wiki/${encodeURIComponent(item.title)}`,
|
|
121
|
+
snippet: item.snippet.replace(/<[^>]*>/g, '').trim(),
|
|
122
|
+
source: 'wikipedia',
|
|
123
|
+
}));
|
|
124
|
+
} catch (err) {
|
|
125
|
+
throw new Error(`Wikipedia search failed: ${err.message}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export default { search };
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Persistent knowledge storage with relevance search.
|
|
3
|
+
* @module vault/knowledge-base
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { promises as fs } from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import crypto from 'node:crypto';
|
|
9
|
+
|
|
10
|
+
export class KnowledgeBase {
|
|
11
|
+
/**
|
|
12
|
+
* @param {Object} [options]
|
|
13
|
+
* @param {string} [options.storagePath='.prepia/knowledge'] - Storage directory
|
|
14
|
+
* @param {number} [options.maxEntries=10000] - Max entries
|
|
15
|
+
* @param {number} [options.defaultTTL=86400000] - Default TTL (24h)
|
|
16
|
+
*/
|
|
17
|
+
constructor(options = {}) {
|
|
18
|
+
this._storagePath = options.storagePath ?? '.prepia/knowledge';
|
|
19
|
+
this._maxEntries = options.maxEntries ?? 10000;
|
|
20
|
+
this._defaultTTL = options.defaultTTL ?? 24 * 60 * 60 * 1000;
|
|
21
|
+
/** @type {Map<string, Object>} */
|
|
22
|
+
this._entries = new Map();
|
|
23
|
+
this._initialized = false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Initialize - load from disk if available.
|
|
28
|
+
*/
|
|
29
|
+
async init() {
|
|
30
|
+
if (this._initialized) return;
|
|
31
|
+
try {
|
|
32
|
+
await fs.mkdir(this._storagePath, { recursive: true });
|
|
33
|
+
const indexFile = path.join(this._storagePath, 'index.json');
|
|
34
|
+
const data = await fs.readFile(indexFile, 'utf-8');
|
|
35
|
+
const entries = JSON.parse(data);
|
|
36
|
+
for (const entry of entries) {
|
|
37
|
+
this._entries.set(entry.key, entry);
|
|
38
|
+
}
|
|
39
|
+
} catch {
|
|
40
|
+
// No existing data or parse error
|
|
41
|
+
}
|
|
42
|
+
this._initialized = true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Store knowledge.
|
|
47
|
+
* @param {string} key - Unique key
|
|
48
|
+
* @param {*} value - Value to store
|
|
49
|
+
* @param {Object} [options]
|
|
50
|
+
* @param {string[]} [options.tags] - Tags for categorization
|
|
51
|
+
* @param {string} [options.source] - Source of the knowledge
|
|
52
|
+
* @param {number} [options.ttl] - TTL in ms
|
|
53
|
+
* @param {number} [options.confidence=1] - Confidence score (0-1)
|
|
54
|
+
*/
|
|
55
|
+
async store(key, value, options = {}) {
|
|
56
|
+
await this.init();
|
|
57
|
+
const now = Date.now();
|
|
58
|
+
this._entries.set(key, {
|
|
59
|
+
key,
|
|
60
|
+
value,
|
|
61
|
+
tags: options.tags || [],
|
|
62
|
+
source: options.source || '',
|
|
63
|
+
confidence: options.confidence ?? 1,
|
|
64
|
+
createdAt: now,
|
|
65
|
+
updatedAt: now,
|
|
66
|
+
expiresAt: now + (options.ttl ?? this._defaultTTL),
|
|
67
|
+
accessCount: 0,
|
|
68
|
+
lastAccessed: null,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Evict if over limit
|
|
72
|
+
if (this._entries.size > this._maxEntries) {
|
|
73
|
+
const oldest = Array.from(this._entries.values())
|
|
74
|
+
.sort((a, b) => a.updatedAt - b.updatedAt)[0];
|
|
75
|
+
if (oldest) this._entries.delete(oldest.key);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
await this._persist();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Retrieve knowledge by key.
|
|
83
|
+
* @param {string} key
|
|
84
|
+
* @returns {Promise<*>} The stored value or undefined
|
|
85
|
+
*/
|
|
86
|
+
async get(key) {
|
|
87
|
+
await this.init();
|
|
88
|
+
const entry = this._entries.get(key);
|
|
89
|
+
if (!entry) return undefined;
|
|
90
|
+
if (Date.now() > entry.expiresAt) {
|
|
91
|
+
this._entries.delete(key);
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
entry.accessCount++;
|
|
95
|
+
entry.lastAccessed = Date.now();
|
|
96
|
+
return entry.value;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Search knowledge by relevance.
|
|
101
|
+
* @param {string} query - Search query
|
|
102
|
+
* @param {Object} [options]
|
|
103
|
+
* @param {string[]} [options.tags] - Filter by tags
|
|
104
|
+
* @param {number} [options.limit=10] - Max results
|
|
105
|
+
* @returns {Promise<Object[]>}
|
|
106
|
+
*/
|
|
107
|
+
async search(query, options = {}) {
|
|
108
|
+
await this.init();
|
|
109
|
+
const { tags, limit = 10 } = options;
|
|
110
|
+
const now = Date.now();
|
|
111
|
+
const queryLower = query.toLowerCase();
|
|
112
|
+
const queryWords = queryLower.split(/\s+/).filter(w => w.length > 2);
|
|
113
|
+
|
|
114
|
+
const scored = [];
|
|
115
|
+
for (const entry of this._entries.values()) {
|
|
116
|
+
if (now > entry.expiresAt) continue;
|
|
117
|
+
if (tags && !tags.some(t => entry.tags.includes(t))) continue;
|
|
118
|
+
|
|
119
|
+
let score = 0;
|
|
120
|
+
const valueStr = typeof entry.value === 'string' ? entry.value : JSON.stringify(entry.value);
|
|
121
|
+
const valueLower = valueStr.toLowerCase();
|
|
122
|
+
|
|
123
|
+
// Exact match bonus
|
|
124
|
+
if (valueLower.includes(queryLower)) score += 10;
|
|
125
|
+
|
|
126
|
+
// Word match
|
|
127
|
+
for (const word of queryWords) {
|
|
128
|
+
if (valueLower.includes(word)) score += 2;
|
|
129
|
+
if (entry.key.toLowerCase().includes(word)) score += 3;
|
|
130
|
+
if (entry.tags.some(t => t.toLowerCase().includes(word))) score += 5;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Confidence and recency boost
|
|
134
|
+
score *= entry.confidence;
|
|
135
|
+
score += (entry.accessCount || 0) * 0.1;
|
|
136
|
+
|
|
137
|
+
if (score > 0) {
|
|
138
|
+
scored.push({ ...entry, score });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
scored.sort((a, b) => b.score - a.score);
|
|
143
|
+
return scored.slice(0, limit).map(({ score, ...rest }) => rest);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Delete knowledge by key.
|
|
148
|
+
* @param {string} key
|
|
149
|
+
* @returns {Promise<boolean>}
|
|
150
|
+
*/
|
|
151
|
+
async delete(key) {
|
|
152
|
+
await this.init();
|
|
153
|
+
const deleted = this._entries.delete(key);
|
|
154
|
+
if (deleted) await this._persist();
|
|
155
|
+
return deleted;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Get the number of entries.
|
|
160
|
+
* @returns {Promise<number>}
|
|
161
|
+
*/
|
|
162
|
+
async size() {
|
|
163
|
+
await this.init();
|
|
164
|
+
return this._entries.size;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Clear all knowledge.
|
|
169
|
+
*/
|
|
170
|
+
async clear() {
|
|
171
|
+
this._entries.clear();
|
|
172
|
+
await this._persist();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Remove expired entries.
|
|
177
|
+
* @returns {Promise<number>}
|
|
178
|
+
*/
|
|
179
|
+
async cleanup() {
|
|
180
|
+
await this.init();
|
|
181
|
+
const now = Date.now();
|
|
182
|
+
let removed = 0;
|
|
183
|
+
for (const [key, entry] of this._entries) {
|
|
184
|
+
if (now > entry.expiresAt) {
|
|
185
|
+
this._entries.delete(key);
|
|
186
|
+
removed++;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (removed > 0) await this._persist();
|
|
190
|
+
return removed;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Persist to disk.
|
|
195
|
+
* @private
|
|
196
|
+
*/
|
|
197
|
+
async _persist() {
|
|
198
|
+
try {
|
|
199
|
+
const indexFile = path.join(this._storagePath, 'index.json');
|
|
200
|
+
await fs.writeFile(indexFile, JSON.stringify(Array.from(this._entries.values()), null, 2));
|
|
201
|
+
} catch {
|
|
202
|
+
// Silently fail persistence
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export default KnowledgeBase;
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Learns user query patterns over time.
|
|
3
|
+
* @module vault/pattern-learner
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export class PatternLearner {
|
|
7
|
+
/**
|
|
8
|
+
* @param {Object} [options]
|
|
9
|
+
* @param {number} [options.maxPatterns=500] - Max patterns to track
|
|
10
|
+
* @param {number} [options.minOccurrences=2] - Min occurrences to be a pattern
|
|
11
|
+
*/
|
|
12
|
+
constructor(options = {}) {
|
|
13
|
+
this._maxPatterns = options.maxPatterns ?? 500;
|
|
14
|
+
this._minOccurrences = options.minOccurrences ?? 2;
|
|
15
|
+
/** @type {Map<string, Object>} */
|
|
16
|
+
this._patterns = new Map();
|
|
17
|
+
/** @type {string[]} Recent query history */
|
|
18
|
+
this._history = [];
|
|
19
|
+
this._maxHistory = 100;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Record a query and its outcome.
|
|
24
|
+
* @param {string} query - User query
|
|
25
|
+
* @param {Object} outcome - { type, tools, success, duration }
|
|
26
|
+
*/
|
|
27
|
+
record(query, outcome = {}) {
|
|
28
|
+
const normalized = this._normalize(query);
|
|
29
|
+
const existing = this._patterns.get(normalized);
|
|
30
|
+
|
|
31
|
+
if (existing) {
|
|
32
|
+
existing.count++;
|
|
33
|
+
existing.lastSeen = Date.now();
|
|
34
|
+
existing.successRate = ((existing.successRate * (existing.count - 1)) + (outcome.success ? 1 : 0)) / existing.count;
|
|
35
|
+
if (outcome.tools) {
|
|
36
|
+
for (const tool of outcome.tools) {
|
|
37
|
+
existing.tools.set(tool, (existing.tools.get(tool) || 0) + 1);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
const tools = new Map();
|
|
42
|
+
if (outcome.tools) {
|
|
43
|
+
for (const tool of outcome.tools) {
|
|
44
|
+
tools.set(tool, 1);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
this._patterns.set(normalized, {
|
|
48
|
+
pattern: normalized,
|
|
49
|
+
original: query,
|
|
50
|
+
count: 1,
|
|
51
|
+
firstSeen: Date.now(),
|
|
52
|
+
lastSeen: Date.now(),
|
|
53
|
+
successRate: outcome.success ? 1 : 0,
|
|
54
|
+
tools,
|
|
55
|
+
type: outcome.type || 'unknown',
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
this._history.push({ query, timestamp: Date.now(), ...outcome });
|
|
60
|
+
if (this._history.length > this._maxHistory) {
|
|
61
|
+
this._history.shift();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Evict old patterns if over limit
|
|
65
|
+
if (this._patterns.size > this._maxPatterns) {
|
|
66
|
+
const oldest = Array.from(this._patterns.values())
|
|
67
|
+
.filter(p => p.count < this._minOccurrences)
|
|
68
|
+
.sort((a, b) => a.lastSeen - b.lastSeen)[0];
|
|
69
|
+
if (oldest) this._patterns.delete(oldest.pattern);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get patterns similar to a query.
|
|
75
|
+
* @param {string} query
|
|
76
|
+
* @param {number} [limit=5]
|
|
77
|
+
* @returns {Object[]}
|
|
78
|
+
*/
|
|
79
|
+
findSimilar(query, limit = 5) {
|
|
80
|
+
const normalized = this._normalize(query);
|
|
81
|
+
const queryWords = normalized.split(/\s+/);
|
|
82
|
+
|
|
83
|
+
const scored = [];
|
|
84
|
+
for (const pattern of this._patterns.values()) {
|
|
85
|
+
if (pattern.count < this._minOccurrences) continue;
|
|
86
|
+
const patternWords = pattern.pattern.split(/\s+/);
|
|
87
|
+
let overlap = 0;
|
|
88
|
+
for (const word of queryWords) {
|
|
89
|
+
if (patternWords.includes(word)) overlap++;
|
|
90
|
+
}
|
|
91
|
+
const similarity = overlap / Math.max(queryWords.length, patternWords.length);
|
|
92
|
+
if (similarity > 0.2) {
|
|
93
|
+
scored.push({ ...pattern, similarity, tools: Object.fromEntries(pattern.tools || new Map()) });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
scored.sort((a, b) => b.similarity - a.similarity || b.count - a.count);
|
|
98
|
+
return scored.slice(0, limit);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Predict likely next queries based on recent history.
|
|
103
|
+
* @param {number} [limit=3]
|
|
104
|
+
* @returns {string[]}
|
|
105
|
+
*/
|
|
106
|
+
predictNext(limit = 3) {
|
|
107
|
+
if (this._history.length < 2) return [];
|
|
108
|
+
|
|
109
|
+
const recent = this._history.slice(-5).map(h => this._normalize(h.query));
|
|
110
|
+
const predictions = new Map();
|
|
111
|
+
|
|
112
|
+
for (const pattern of this._patterns.values()) {
|
|
113
|
+
if (pattern.count < this._minOccurrences) continue;
|
|
114
|
+
for (const recentQuery of recent) {
|
|
115
|
+
const similarity = this._similarity(pattern.pattern, recentQuery);
|
|
116
|
+
if (similarity > 0.3 && similarity < 1.0) {
|
|
117
|
+
predictions.set(pattern.pattern, (predictions.get(pattern.pattern) || 0) + similarity * pattern.count);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return Array.from(predictions.entries())
|
|
123
|
+
.sort((a, b) => b[1] - a[1])
|
|
124
|
+
.slice(0, limit)
|
|
125
|
+
.map(([pattern]) => pattern);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Get all learned patterns.
|
|
130
|
+
* @returns {Object[]}
|
|
131
|
+
*/
|
|
132
|
+
getAllPatterns() {
|
|
133
|
+
return Array.from(this._patterns.values())
|
|
134
|
+
.filter(p => p.count >= this._minOccurrences)
|
|
135
|
+
.sort((a, b) => b.count - a.count)
|
|
136
|
+
.map(p => ({ ...p, tools: Object.fromEntries(p.tools || new Map()) }));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Get recent query history.
|
|
141
|
+
* @param {number} [limit=20]
|
|
142
|
+
* @returns {Object[]}
|
|
143
|
+
*/
|
|
144
|
+
getHistory(limit = 20) {
|
|
145
|
+
return this._history.slice(-limit);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Get the number of tracked patterns.
|
|
150
|
+
* @returns {number}
|
|
151
|
+
*/
|
|
152
|
+
get size() {
|
|
153
|
+
return this._patterns.size;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Clear all patterns and history.
|
|
158
|
+
*/
|
|
159
|
+
clear() {
|
|
160
|
+
this._patterns.clear();
|
|
161
|
+
this._history = [];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Normalize a query for pattern matching.
|
|
166
|
+
* @param {string} query
|
|
167
|
+
* @returns {string}
|
|
168
|
+
* @private
|
|
169
|
+
*/
|
|
170
|
+
_normalize(query) {
|
|
171
|
+
return query.toLowerCase().trim().replace(/[^\w\s]/g, '').replace(/\s+/g, ' ');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Calculate similarity between two strings.
|
|
176
|
+
* @param {string} a
|
|
177
|
+
* @param {string} b
|
|
178
|
+
* @returns {number} 0-1
|
|
179
|
+
* @private
|
|
180
|
+
*/
|
|
181
|
+
_similarity(a, b) {
|
|
182
|
+
const wordsA = new Set(a.split(/\s+/));
|
|
183
|
+
const wordsB = new Set(b.split(/\s+/));
|
|
184
|
+
let intersection = 0;
|
|
185
|
+
for (const word of wordsA) {
|
|
186
|
+
if (wordsB.has(word)) intersection++;
|
|
187
|
+
}
|
|
188
|
+
return intersection / Math.max(wordsA.size, wordsB.size);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export default PatternLearner;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "analyze",
|
|
3
|
+
"description": "Data gathering and analysis",
|
|
4
|
+
"mode": "shot",
|
|
5
|
+
"steps": [
|
|
6
|
+
{
|
|
7
|
+
"keyword": "SEARCH",
|
|
8
|
+
"description": "Gather relevant data",
|
|
9
|
+
"params": { "maxResults": 10 }
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"keyword": "EXTRACT",
|
|
13
|
+
"description": "Extract numerical data and facts",
|
|
14
|
+
"params": { "extractNumbers": true }
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"keyword": "FORMAT",
|
|
18
|
+
"description": "Structure data for analysis",
|
|
19
|
+
"params": { "format": "json" }
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"keyword": "DELIVER",
|
|
23
|
+
"description": "Analyze and present findings",
|
|
24
|
+
"params": { "includeCharts": false, "includeInsights": true }
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
"options": {
|
|
28
|
+
"enableCalculation": true,
|
|
29
|
+
"enableVisualization": false,
|
|
30
|
+
"outputFormat": "markdown"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "automate",
|
|
3
|
+
"description": "Multi-step task automation",
|
|
4
|
+
"mode": "shot",
|
|
5
|
+
"steps": [
|
|
6
|
+
{
|
|
7
|
+
"keyword": "SEARCH",
|
|
8
|
+
"description": "Research automation approach",
|
|
9
|
+
"params": { "maxResults": 5 }
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"keyword": "EXTRACT",
|
|
13
|
+
"description": "Extract relevant procedures and methods",
|
|
14
|
+
"params": { "maxLength": 20000 }
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"keyword": "FORMAT",
|
|
18
|
+
"description": "Structure automation plan",
|
|
19
|
+
"params": { "format": "steps" }
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"keyword": "DELIVER",
|
|
23
|
+
"description": "Generate automation instructions",
|
|
24
|
+
"params": { "stepByStep": true, "includeWarnings": true }
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
"options": {
|
|
28
|
+
"enableExecution": false,
|
|
29
|
+
"dryRun": true,
|
|
30
|
+
"outputFormat": "markdown"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "research",
|
|
3
|
+
"description": "Multi-source web research with fact-checking",
|
|
4
|
+
"mode": "shot",
|
|
5
|
+
"steps": [
|
|
6
|
+
{
|
|
7
|
+
"keyword": "SEARCH",
|
|
8
|
+
"description": "Search multiple sources for information",
|
|
9
|
+
"params": { "engines": ["duckduckgo", "wikipedia"], "maxResults": 5 }
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"keyword": "EXTRACT",
|
|
13
|
+
"description": "Extract key content from search results",
|
|
14
|
+
"params": { "maxLength": 10000 }
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"keyword": "FILTER",
|
|
18
|
+
"description": "Filter for relevance and recency",
|
|
19
|
+
"params": { "minRelevance": 0.3 }
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"keyword": "FORMAT",
|
|
23
|
+
"description": "Format findings for LLM consumption",
|
|
24
|
+
"params": { "format": "structured" }
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"keyword": "DELIVER",
|
|
28
|
+
"description": "Synthesize final answer with LLM",
|
|
29
|
+
"params": { "verify": true, "cite": true }
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"options": {
|
|
33
|
+
"enableFactCheck": true,
|
|
34
|
+
"maxTokens": 4000,
|
|
35
|
+
"outputFormat": "markdown"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "summarize",
|
|
3
|
+
"description": "Content compression and summarization",
|
|
4
|
+
"mode": "shot",
|
|
5
|
+
"steps": [
|
|
6
|
+
{
|
|
7
|
+
"keyword": "EXTRACT",
|
|
8
|
+
"description": "Extract main content from source",
|
|
9
|
+
"params": { "stripHtml": true, "maxLength": 50000 }
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"keyword": "FILTER",
|
|
13
|
+
"description": "Remove boilerplate and noise",
|
|
14
|
+
"params": { "removeAds": true, "removeNav": true }
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"keyword": "FORMAT",
|
|
18
|
+
"description": "Prepare for summarization",
|
|
19
|
+
"params": { "format": "text" }
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"keyword": "DELIVER",
|
|
23
|
+
"description": "Generate summary with LLM",
|
|
24
|
+
"params": { "style": "concise", "maxLength": 500 }
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
"options": {
|
|
28
|
+
"compressionRatio": 0.2,
|
|
29
|
+
"preserveKeyPoints": true,
|
|
30
|
+
"outputFormat": "text"
|
|
31
|
+
}
|
|
32
|
+
}
|