beast-agent 1.8.0 → 2.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 -21
- package/README.md +130 -128
- package/bin/beast-agent.js +137 -137
- package/package.json +1 -1
- package/scripts/fix-electron.js +138 -138
- package/scripts/release.js +137 -131
- package/scripts/swap-electron.js +40 -40
- package/src/agent/agentdefs.js +122 -122
- package/src/agent/bots.js +580 -580
- package/src/agent/bus.js +389 -389
- package/src/agent/computeruse.js +200 -200
- package/src/agent/config.js +284 -284
- package/src/agent/discord.js +332 -332
- package/src/agent/engine.js +85 -11
- package/src/agent/kb.js +123 -123
- package/src/agent/llm.js +430 -430
- package/src/agent/logger.js +90 -90
- package/src/agent/mcp.js +427 -0
- package/src/agent/mem0.js +605 -605
- package/src/agent/memory.js +427 -427
- package/src/agent/mqueue.js +124 -124
- package/src/agent/pdf.js +20 -20
- package/src/agent/research.js +133 -133
- package/src/agent/scripts/news.py +113 -113
- package/src/agent/scripts/stealthsearch.py +30 -30
- package/src/agent/scripts/websearch.py +225 -225
- package/src/agent/searxng.js +325 -325
- package/src/agent/seeds/brainstorming/SKILL.md +90 -90
- package/src/agent/seeds/dispatching-parallel-agents/SKILL.md +120 -120
- package/src/agent/seeds/executing-plans/SKILL.md +60 -60
- package/src/agent/seeds/subagent-driven-development/SKILL.md +167 -167
- package/src/agent/seeds/systematic-debugging/SKILL.md +131 -131
- package/src/agent/seeds/test-driven-development/SKILL.md +152 -152
- package/src/agent/seeds/verification-before-completion/SKILL.md +63 -63
- package/src/agent/seeds/writing-plans/SKILL.md +162 -162
- package/src/agent/seeds/writing-skills/SKILL.md +229 -229
- package/src/agent/skills.js +652 -651
- package/src/agent/store.js +378 -378
- package/src/agent/telegram.js +155 -155
- package/src/agent/tokens.js +39 -39
- package/src/agent/usage.js +125 -125
- package/src/agent/watchers.js +312 -312
- package/src/agent/watext.js +80 -80
- package/src/agent/whatsapp.js +555 -555
- package/src/cron.js +255 -255
- package/src/main.js +385 -6
- package/src/preload.js +13 -0
- package/src/renderer/browserPreload.js +73 -73
- package/src/renderer/i18n.js +38 -0
- package/src/renderer/index.html +448 -407
- package/src/renderer/renderer.js +897 -42
- package/src/renderer/style.css +275 -11
package/src/agent/mqueue.js
CHANGED
|
@@ -1,124 +1,124 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/* OFFLINE MESAJ KUYRUĞU (FEATURE 2)
|
|
4
|
-
İnternet/elektrik kesintisinde WhatsApp mesajları kaybolmasın:
|
|
5
|
-
- Gönderilemeyen mesaj dosyaya yazılır (%APPDATA%\beast\message_queue.json)
|
|
6
|
-
- Bağlantı gelince sırayla gönderilir, gönderilenler kuyruktan silinir
|
|
7
|
-
- Retry: 30sn → 1dk → 5dk → 15dk → 30dk backoff; 5 denemeden sonra "failed"
|
|
8
|
-
- Dosya tabanlı olduğu için elektrik kesintisinde veri korunur */
|
|
9
|
-
|
|
10
|
-
const fs = require('fs');
|
|
11
|
-
const path = require('path');
|
|
12
|
-
const os = require('os');
|
|
13
|
-
|
|
14
|
-
const MAX_QUEUE = 100; // kuyruk üst sınırı — taşarsa eski (öncelikle işlenmiş) kayıtlar düşer
|
|
15
|
-
const MAX_RETRY = 5;
|
|
16
|
-
const BACKOFF_MS = [30e3, 60e3, 300e3, 900e3, 1800e3]; // 30sn, 1dk, 5dk, 15dk, 30dk
|
|
17
|
-
|
|
18
|
-
function root() {
|
|
19
|
-
if (process.env.BEAST_DATA) return process.env.BEAST_DATA;
|
|
20
|
-
return process.env.APPDATA
|
|
21
|
-
? path.join(process.env.APPDATA, 'beast')
|
|
22
|
-
: path.join(os.homedir(), 'AppData', 'Roaming', 'beast');
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function file() {
|
|
26
|
-
return path.join(root(), 'message_queue.json');
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
let CACHE = null;
|
|
30
|
-
|
|
31
|
-
function load() {
|
|
32
|
-
if (CACHE) return CACHE;
|
|
33
|
-
try {
|
|
34
|
-
const j = JSON.parse(fs.readFileSync(file(), 'utf8'));
|
|
35
|
-
CACHE = Array.isArray(j.items) ? j.items : [];
|
|
36
|
-
} catch {
|
|
37
|
-
CACHE = [];
|
|
38
|
-
}
|
|
39
|
-
return CACHE;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function save() {
|
|
43
|
-
try {
|
|
44
|
-
fs.mkdirSync(root(), { recursive: true });
|
|
45
|
-
const tmp = file() + '.tmp';
|
|
46
|
-
fs.writeFileSync(tmp, JSON.stringify({ items: CACHE }, null, 2));
|
|
47
|
-
fs.renameSync(tmp, file()); // atomik yazım — yarı kalmış dosya kirliliği olmaz
|
|
48
|
-
} catch {}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/* Gönderilemeyen mesajı kuyruğa al */
|
|
52
|
-
function add({ to, body }) {
|
|
53
|
-
const list = load();
|
|
54
|
-
const item = {
|
|
55
|
-
id: 'q' + Date.now().toString(36) + Math.random().toString(36).slice(2, 6),
|
|
56
|
-
timestamp: new Date().toISOString(),
|
|
57
|
-
to: String(to || ''),
|
|
58
|
-
body: String(body || '').slice(0, 4000),
|
|
59
|
-
status: 'pending',
|
|
60
|
-
retry_count: 0,
|
|
61
|
-
nextAt: 0, // hemen denenmeye hazır
|
|
62
|
-
};
|
|
63
|
-
list.push(item);
|
|
64
|
-
/* taşma koruması: önce sent/failed kayıtlarını, sonra en eski pending'i düşür */
|
|
65
|
-
while (list.length > MAX_QUEUE) {
|
|
66
|
-
const idx = list.findIndex((x) => x.status === 'sent' || x.status === 'failed');
|
|
67
|
-
if (idx >= 0) list.splice(idx, 1);
|
|
68
|
-
else list.shift();
|
|
69
|
-
}
|
|
70
|
-
save();
|
|
71
|
-
return item;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/* Şu an denenecek kuyruk öğeleri (backoff süresi dolmuş pending'ler, sırayla) */
|
|
75
|
-
function due() {
|
|
76
|
-
const now = Date.now();
|
|
77
|
-
return load()
|
|
78
|
-
.filter((x) => x.status === 'pending' && (x.nextAt || 0) <= now)
|
|
79
|
-
.sort((a, b) => String(a.timestamp).localeCompare(String(b.timestamp)));
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/* Gönderildi → kuyruktan sil */
|
|
83
|
-
function markSent(id) {
|
|
84
|
-
const list = load();
|
|
85
|
-
const idx = list.findIndex((x) => x.id === id);
|
|
86
|
-
if (idx >= 0) {
|
|
87
|
-
list.splice(idx, 1);
|
|
88
|
-
save();
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/* Deneme başarısız → backoff planla; 5 denemeden sonra "failed" (gonderilemedi) */
|
|
93
|
-
function bumpRetry(id) {
|
|
94
|
-
const it = load().find((x) => x.id === id);
|
|
95
|
-
if (!it) return;
|
|
96
|
-
it.retry_count = (it.retry_count || 0) + 1;
|
|
97
|
-
if (it.retry_count >= MAX_RETRY) {
|
|
98
|
-
it.status = 'failed';
|
|
99
|
-
it.failedAt = new Date().toISOString();
|
|
100
|
-
} else {
|
|
101
|
-
it.nextAt = Date.now() + BACKOFF_MS[it.retry_count - 1];
|
|
102
|
-
}
|
|
103
|
-
save();
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function pendingCount() {
|
|
107
|
-
return load().filter((x) => x.status === 'pending').length;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function stats() {
|
|
111
|
-
const l = load();
|
|
112
|
-
return {
|
|
113
|
-
pending: l.filter((x) => x.status === 'pending').length,
|
|
114
|
-
failed: l.filter((x) => x.status === 'failed').length,
|
|
115
|
-
total: l.length,
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/* bağlantı geri gelince testler için */
|
|
120
|
-
function _reset() {
|
|
121
|
-
CACHE = null;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
module.exports = { MAX_QUEUE, MAX_RETRY, BACKOFF_MS, add, due, markSent, bumpRetry, pendingCount, stats, _reset, file };
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/* OFFLINE MESAJ KUYRUĞU (FEATURE 2)
|
|
4
|
+
İnternet/elektrik kesintisinde WhatsApp mesajları kaybolmasın:
|
|
5
|
+
- Gönderilemeyen mesaj dosyaya yazılır (%APPDATA%\beast\message_queue.json)
|
|
6
|
+
- Bağlantı gelince sırayla gönderilir, gönderilenler kuyruktan silinir
|
|
7
|
+
- Retry: 30sn → 1dk → 5dk → 15dk → 30dk backoff; 5 denemeden sonra "failed"
|
|
8
|
+
- Dosya tabanlı olduğu için elektrik kesintisinde veri korunur */
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const os = require('os');
|
|
13
|
+
|
|
14
|
+
const MAX_QUEUE = 100; // kuyruk üst sınırı — taşarsa eski (öncelikle işlenmiş) kayıtlar düşer
|
|
15
|
+
const MAX_RETRY = 5;
|
|
16
|
+
const BACKOFF_MS = [30e3, 60e3, 300e3, 900e3, 1800e3]; // 30sn, 1dk, 5dk, 15dk, 30dk
|
|
17
|
+
|
|
18
|
+
function root() {
|
|
19
|
+
if (process.env.BEAST_DATA) return process.env.BEAST_DATA;
|
|
20
|
+
return process.env.APPDATA
|
|
21
|
+
? path.join(process.env.APPDATA, 'beast')
|
|
22
|
+
: path.join(os.homedir(), 'AppData', 'Roaming', 'beast');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function file() {
|
|
26
|
+
return path.join(root(), 'message_queue.json');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let CACHE = null;
|
|
30
|
+
|
|
31
|
+
function load() {
|
|
32
|
+
if (CACHE) return CACHE;
|
|
33
|
+
try {
|
|
34
|
+
const j = JSON.parse(fs.readFileSync(file(), 'utf8'));
|
|
35
|
+
CACHE = Array.isArray(j.items) ? j.items : [];
|
|
36
|
+
} catch {
|
|
37
|
+
CACHE = [];
|
|
38
|
+
}
|
|
39
|
+
return CACHE;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function save() {
|
|
43
|
+
try {
|
|
44
|
+
fs.mkdirSync(root(), { recursive: true });
|
|
45
|
+
const tmp = file() + '.tmp';
|
|
46
|
+
fs.writeFileSync(tmp, JSON.stringify({ items: CACHE }, null, 2));
|
|
47
|
+
fs.renameSync(tmp, file()); // atomik yazım — yarı kalmış dosya kirliliği olmaz
|
|
48
|
+
} catch {}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* Gönderilemeyen mesajı kuyruğa al */
|
|
52
|
+
function add({ to, body }) {
|
|
53
|
+
const list = load();
|
|
54
|
+
const item = {
|
|
55
|
+
id: 'q' + Date.now().toString(36) + Math.random().toString(36).slice(2, 6),
|
|
56
|
+
timestamp: new Date().toISOString(),
|
|
57
|
+
to: String(to || ''),
|
|
58
|
+
body: String(body || '').slice(0, 4000),
|
|
59
|
+
status: 'pending',
|
|
60
|
+
retry_count: 0,
|
|
61
|
+
nextAt: 0, // hemen denenmeye hazır
|
|
62
|
+
};
|
|
63
|
+
list.push(item);
|
|
64
|
+
/* taşma koruması: önce sent/failed kayıtlarını, sonra en eski pending'i düşür */
|
|
65
|
+
while (list.length > MAX_QUEUE) {
|
|
66
|
+
const idx = list.findIndex((x) => x.status === 'sent' || x.status === 'failed');
|
|
67
|
+
if (idx >= 0) list.splice(idx, 1);
|
|
68
|
+
else list.shift();
|
|
69
|
+
}
|
|
70
|
+
save();
|
|
71
|
+
return item;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* Şu an denenecek kuyruk öğeleri (backoff süresi dolmuş pending'ler, sırayla) */
|
|
75
|
+
function due() {
|
|
76
|
+
const now = Date.now();
|
|
77
|
+
return load()
|
|
78
|
+
.filter((x) => x.status === 'pending' && (x.nextAt || 0) <= now)
|
|
79
|
+
.sort((a, b) => String(a.timestamp).localeCompare(String(b.timestamp)));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* Gönderildi → kuyruktan sil */
|
|
83
|
+
function markSent(id) {
|
|
84
|
+
const list = load();
|
|
85
|
+
const idx = list.findIndex((x) => x.id === id);
|
|
86
|
+
if (idx >= 0) {
|
|
87
|
+
list.splice(idx, 1);
|
|
88
|
+
save();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* Deneme başarısız → backoff planla; 5 denemeden sonra "failed" (gonderilemedi) */
|
|
93
|
+
function bumpRetry(id) {
|
|
94
|
+
const it = load().find((x) => x.id === id);
|
|
95
|
+
if (!it) return;
|
|
96
|
+
it.retry_count = (it.retry_count || 0) + 1;
|
|
97
|
+
if (it.retry_count >= MAX_RETRY) {
|
|
98
|
+
it.status = 'failed';
|
|
99
|
+
it.failedAt = new Date().toISOString();
|
|
100
|
+
} else {
|
|
101
|
+
it.nextAt = Date.now() + BACKOFF_MS[it.retry_count - 1];
|
|
102
|
+
}
|
|
103
|
+
save();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function pendingCount() {
|
|
107
|
+
return load().filter((x) => x.status === 'pending').length;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function stats() {
|
|
111
|
+
const l = load();
|
|
112
|
+
return {
|
|
113
|
+
pending: l.filter((x) => x.status === 'pending').length,
|
|
114
|
+
failed: l.filter((x) => x.status === 'failed').length,
|
|
115
|
+
total: l.length,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* bağlantı geri gelince testler için */
|
|
120
|
+
function _reset() {
|
|
121
|
+
CACHE = null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
module.exports = { MAX_QUEUE, MAX_RETRY, BACKOFF_MS, add, due, markSent, bumpRetry, pendingCount, stats, _reset, file };
|
package/src/agent/pdf.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/* Beast PDF yardımcısı: pdf-parse v2 ile düz metin çıkarımı.
|
|
4
|
-
pdf-parse kendi pdfjs-dist@5.4.296 kopyasını kullanır (nested). Aynı process'te
|
|
5
|
-
başka bir pdfjs sürümü yükleyen paket (örn. pdf-to-img) açılırsa globalThis
|
|
6
|
-
worker cache çakışmasından "API version ... does not match Worker version"
|
|
7
|
-
hatası çıkar — o yüzden render/OCR işleri AYRI node süreçlerinde yapılır
|
|
8
|
-
(ayrıntı: pdf skill'i). */
|
|
9
|
-
|
|
10
|
-
async function extract(buf) {
|
|
11
|
-
const { PDFParse } = require('pdf-parse');
|
|
12
|
-
const parser = new PDFParse({ data: new Uint8Array(buf) });
|
|
13
|
-
try {
|
|
14
|
-
return await parser.getText();
|
|
15
|
-
} finally {
|
|
16
|
-
await parser.destroy();
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
module.exports = { extract };
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/* Beast PDF yardımcısı: pdf-parse v2 ile düz metin çıkarımı.
|
|
4
|
+
pdf-parse kendi pdfjs-dist@5.4.296 kopyasını kullanır (nested). Aynı process'te
|
|
5
|
+
başka bir pdfjs sürümü yükleyen paket (örn. pdf-to-img) açılırsa globalThis
|
|
6
|
+
worker cache çakışmasından "API version ... does not match Worker version"
|
|
7
|
+
hatası çıkar — o yüzden render/OCR işleri AYRI node süreçlerinde yapılır
|
|
8
|
+
(ayrıntı: pdf skill'i). */
|
|
9
|
+
|
|
10
|
+
async function extract(buf) {
|
|
11
|
+
const { PDFParse } = require('pdf-parse');
|
|
12
|
+
const parser = new PDFParse({ data: new Uint8Array(buf) });
|
|
13
|
+
try {
|
|
14
|
+
return await parser.getText();
|
|
15
|
+
} finally {
|
|
16
|
+
await parser.destroy();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports = { extract };
|
package/src/agent/research.js
CHANGED
|
@@ -1,133 +1,133 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/* BEAST — deep_search: agentic araştırma katmanı.
|
|
4
|
-
web_search snippet'te kalır; bu katman derine iner:
|
|
5
|
-
1) 1-4 sorgu varyantını PARALEL aratır (toplam süre ≈ tek arama)
|
|
6
|
-
2) Sonuçları URL bazlı tekilleştirip round-robin harmanlar (kaynak çeşitliliği)
|
|
7
|
-
3) İlk N sonucu GİZLİ gerçek tarayıcıda açıp tam metin okur
|
|
8
|
-
(deps.readPage verilmişse; yoksa yalnız sonuç listesi döner)
|
|
9
|
-
Saf modüldür: search/readPage enjekte edilir — test edilebilir. */
|
|
10
|
-
|
|
11
|
-
/* İçerik okumada anlamsız/ikili dosyalar — tarayıcıda açmanın anlamı yok */
|
|
12
|
-
const SKIP_FILE_RE = /\.(pdf|docx?|xlsx?|pptx?|zip|rar|7z|exe|dmg|apk|mp[34]|m4a|avi|mkv|webm|jpe?g|png|gif|webp|svg|ico|csv)([?#]|$)/i;
|
|
13
|
-
|
|
14
|
-
/* URL anahtarı: scheme/www/slash/hash farklarını yok sayar — tekilleştirme için */
|
|
15
|
-
function normKey(u) {
|
|
16
|
-
try {
|
|
17
|
-
const x = new URL(u);
|
|
18
|
-
x.hash = '';
|
|
19
|
-
let s = x.toString();
|
|
20
|
-
s = s.replace(/^http:\/\//i, 'https://');
|
|
21
|
-
s = s.replace(/^https:\/\/www\./i, 'https://');
|
|
22
|
-
return s.replace(/\/+$/, '').toLowerCase();
|
|
23
|
-
} catch {
|
|
24
|
-
return String(u || '').toLowerCase();
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/* search() geri dönüşünü normalize eder: dizi YA DA {results:[...]} kabul;
|
|
29
|
-
satır anahtarları title/url/snippet VEYA name/href/link/body/description olabilir */
|
|
30
|
-
function normalizeRows(out) {
|
|
31
|
-
if (!out) return [];
|
|
32
|
-
const rows = Array.isArray(out) ? out : Array.isArray(out.results) ? out.results : [];
|
|
33
|
-
return rows
|
|
34
|
-
.map((r) => ({
|
|
35
|
-
title: String((r && (r.title || r.name)) || '').replace(/\s+/g, ' ').trim(),
|
|
36
|
-
url: String((r && (r.url || r.href || r.link)) || '').trim(),
|
|
37
|
-
snippet: String((r && (r.snippet || r.body || r.description)) || '').replace(/\s+/g, ' ').trim(),
|
|
38
|
-
}))
|
|
39
|
-
.filter((r) => r.url && /^https?:\/\//i.test(r.url) && r.title);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/* Liste harmanı: her listenin i. elemanı sırayla alınır (round-robin) —
|
|
43
|
-
tek motorun sonuçları öne çökmez, kaynak çeşitliliği korunur */
|
|
44
|
-
function mergeResults(lists, limit) {
|
|
45
|
-
const seen = new Set();
|
|
46
|
-
const out = [];
|
|
47
|
-
const max = lists.reduce((m, l) => Math.max(m, l.length), 0);
|
|
48
|
-
for (let i = 0; i < max && out.length < limit; i++) {
|
|
49
|
-
for (const list of lists) {
|
|
50
|
-
if (out.length >= limit) break;
|
|
51
|
-
const r = list[i];
|
|
52
|
-
if (!r) continue;
|
|
53
|
-
const key = normKey(r.url);
|
|
54
|
-
if (seen.has(key)) continue;
|
|
55
|
-
seen.add(key);
|
|
56
|
-
out.push(r);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return out;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/* Eşzamanlılığı sınırlı paralel map — hata tek elemanı bozar, diziyi bozmaz */
|
|
63
|
-
function mapLimit(items, n, fn) {
|
|
64
|
-
const out = new Array(items.length);
|
|
65
|
-
let i = 0;
|
|
66
|
-
const worker = async () => {
|
|
67
|
-
while (i < items.length) {
|
|
68
|
-
const idx = i++;
|
|
69
|
-
try {
|
|
70
|
-
out[idx] = await fn(items[idx], idx);
|
|
71
|
-
} catch (e) {
|
|
72
|
-
out[idx] = { ok: false, url: items[idx] && items[idx].url, error: String((e && e.message) || e) };
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
|
-
const workers = Array.from({ length: Math.max(1, Math.min(n, items.length)) }, worker);
|
|
77
|
-
return Promise.all(workers).then(() => out);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
async function deepSearch(args, deps, signal) {
|
|
81
|
-
const a = args || {};
|
|
82
|
-
let queries = a.queries;
|
|
83
|
-
if (typeof queries === 'string') queries = [queries];
|
|
84
|
-
if (!Array.isArray(queries) || !queries.length) {
|
|
85
|
-
const q = String(a.query || '').trim();
|
|
86
|
-
if (q) queries = [q];
|
|
87
|
-
}
|
|
88
|
-
queries = [...new Set((queries || []).map((s) => String(s || '').trim()).filter(Boolean))].slice(0, 4);
|
|
89
|
-
if (!queries.length) return { ok: false, error: 'sorgu yok — queries: ["...", "..."] ver' };
|
|
90
|
-
if (signal && signal.aborted) return { ok: false, error: 'iptal edildi', queries };
|
|
91
|
-
|
|
92
|
-
const maxResults = Math.max(5, Math.min(25, Number(a.max_results) || 16));
|
|
93
|
-
const readTop = Math.max(0, Math.min(6, Number(a.read_top != null ? a.read_top : (a.readTop != null ? a.readTop : 3))));
|
|
94
|
-
const search = deps && typeof deps.search === 'function' ? deps.search : null;
|
|
95
|
-
if (!search) return { ok: false, error: 'arama altyapısı yok', queries };
|
|
96
|
-
|
|
97
|
-
/* 1) paralel arama — bir sorgu çökse diğerleri devam eder */
|
|
98
|
-
const lists = await Promise.all(
|
|
99
|
-
queries.map((q) =>
|
|
100
|
-
Promise.resolve()
|
|
101
|
-
.then(() => search(q))
|
|
102
|
-
.then(normalizeRows)
|
|
103
|
-
.catch(() => [])
|
|
104
|
-
)
|
|
105
|
-
);
|
|
106
|
-
const results = mergeResults(lists, maxResults);
|
|
107
|
-
|
|
108
|
-
/* 2) ilk N sonucu gizli tarayıcıda açıp oku (ikili dosyaları atla) */
|
|
109
|
-
let pages = [];
|
|
110
|
-
if (readTop && results.length && deps && typeof deps.readPage === 'function') {
|
|
111
|
-
const targets = results.filter((r) => !SKIP_FILE_RE.test(r.url)).slice(0, readTop);
|
|
112
|
-
if (targets.length) {
|
|
113
|
-
pages = (await mapLimit(targets, 2, (r) => deps.readPage(r.url))).filter(Boolean);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
const anyPage = pages.some((p) => p && p.ok);
|
|
117
|
-
|
|
118
|
-
return {
|
|
119
|
-
ok: !!(results.length || anyPage),
|
|
120
|
-
queries,
|
|
121
|
-
results,
|
|
122
|
-
...(pages.length ? { pages } : {}),
|
|
123
|
-
...(results.length || anyPage
|
|
124
|
-
? {
|
|
125
|
-
note: anyPage
|
|
126
|
-
? 'sayfalar gizli tarayıcıda açılıp okundu — excerpt\u2019ler pages içinde; bir sayfayı kullanıcıya da göstermek istersen browser_open ile panelde aç'
|
|
127
|
-
: 'sonuç listesi hazır — tam metin okuma kapalı/başarısız; URL\u2019leri http_fetch ile okuyabilirsin',
|
|
128
|
-
}
|
|
129
|
-
: { error: 'tüm motorlar boş döndü — sorguları farklı ifade etmeyi dene' }),
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
module.exports = { deepSearch, mergeResults, normalizeRows, normKey, mapLimit, SKIP_FILE_RE };
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/* BEAST — deep_search: agentic araştırma katmanı.
|
|
4
|
+
web_search snippet'te kalır; bu katman derine iner:
|
|
5
|
+
1) 1-4 sorgu varyantını PARALEL aratır (toplam süre ≈ tek arama)
|
|
6
|
+
2) Sonuçları URL bazlı tekilleştirip round-robin harmanlar (kaynak çeşitliliği)
|
|
7
|
+
3) İlk N sonucu GİZLİ gerçek tarayıcıda açıp tam metin okur
|
|
8
|
+
(deps.readPage verilmişse; yoksa yalnız sonuç listesi döner)
|
|
9
|
+
Saf modüldür: search/readPage enjekte edilir — test edilebilir. */
|
|
10
|
+
|
|
11
|
+
/* İçerik okumada anlamsız/ikili dosyalar — tarayıcıda açmanın anlamı yok */
|
|
12
|
+
const SKIP_FILE_RE = /\.(pdf|docx?|xlsx?|pptx?|zip|rar|7z|exe|dmg|apk|mp[34]|m4a|avi|mkv|webm|jpe?g|png|gif|webp|svg|ico|csv)([?#]|$)/i;
|
|
13
|
+
|
|
14
|
+
/* URL anahtarı: scheme/www/slash/hash farklarını yok sayar — tekilleştirme için */
|
|
15
|
+
function normKey(u) {
|
|
16
|
+
try {
|
|
17
|
+
const x = new URL(u);
|
|
18
|
+
x.hash = '';
|
|
19
|
+
let s = x.toString();
|
|
20
|
+
s = s.replace(/^http:\/\//i, 'https://');
|
|
21
|
+
s = s.replace(/^https:\/\/www\./i, 'https://');
|
|
22
|
+
return s.replace(/\/+$/, '').toLowerCase();
|
|
23
|
+
} catch {
|
|
24
|
+
return String(u || '').toLowerCase();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* search() geri dönüşünü normalize eder: dizi YA DA {results:[...]} kabul;
|
|
29
|
+
satır anahtarları title/url/snippet VEYA name/href/link/body/description olabilir */
|
|
30
|
+
function normalizeRows(out) {
|
|
31
|
+
if (!out) return [];
|
|
32
|
+
const rows = Array.isArray(out) ? out : Array.isArray(out.results) ? out.results : [];
|
|
33
|
+
return rows
|
|
34
|
+
.map((r) => ({
|
|
35
|
+
title: String((r && (r.title || r.name)) || '').replace(/\s+/g, ' ').trim(),
|
|
36
|
+
url: String((r && (r.url || r.href || r.link)) || '').trim(),
|
|
37
|
+
snippet: String((r && (r.snippet || r.body || r.description)) || '').replace(/\s+/g, ' ').trim(),
|
|
38
|
+
}))
|
|
39
|
+
.filter((r) => r.url && /^https?:\/\//i.test(r.url) && r.title);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* Liste harmanı: her listenin i. elemanı sırayla alınır (round-robin) —
|
|
43
|
+
tek motorun sonuçları öne çökmez, kaynak çeşitliliği korunur */
|
|
44
|
+
function mergeResults(lists, limit) {
|
|
45
|
+
const seen = new Set();
|
|
46
|
+
const out = [];
|
|
47
|
+
const max = lists.reduce((m, l) => Math.max(m, l.length), 0);
|
|
48
|
+
for (let i = 0; i < max && out.length < limit; i++) {
|
|
49
|
+
for (const list of lists) {
|
|
50
|
+
if (out.length >= limit) break;
|
|
51
|
+
const r = list[i];
|
|
52
|
+
if (!r) continue;
|
|
53
|
+
const key = normKey(r.url);
|
|
54
|
+
if (seen.has(key)) continue;
|
|
55
|
+
seen.add(key);
|
|
56
|
+
out.push(r);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/* Eşzamanlılığı sınırlı paralel map — hata tek elemanı bozar, diziyi bozmaz */
|
|
63
|
+
function mapLimit(items, n, fn) {
|
|
64
|
+
const out = new Array(items.length);
|
|
65
|
+
let i = 0;
|
|
66
|
+
const worker = async () => {
|
|
67
|
+
while (i < items.length) {
|
|
68
|
+
const idx = i++;
|
|
69
|
+
try {
|
|
70
|
+
out[idx] = await fn(items[idx], idx);
|
|
71
|
+
} catch (e) {
|
|
72
|
+
out[idx] = { ok: false, url: items[idx] && items[idx].url, error: String((e && e.message) || e) };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
const workers = Array.from({ length: Math.max(1, Math.min(n, items.length)) }, worker);
|
|
77
|
+
return Promise.all(workers).then(() => out);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function deepSearch(args, deps, signal) {
|
|
81
|
+
const a = args || {};
|
|
82
|
+
let queries = a.queries;
|
|
83
|
+
if (typeof queries === 'string') queries = [queries];
|
|
84
|
+
if (!Array.isArray(queries) || !queries.length) {
|
|
85
|
+
const q = String(a.query || '').trim();
|
|
86
|
+
if (q) queries = [q];
|
|
87
|
+
}
|
|
88
|
+
queries = [...new Set((queries || []).map((s) => String(s || '').trim()).filter(Boolean))].slice(0, 4);
|
|
89
|
+
if (!queries.length) return { ok: false, error: 'sorgu yok — queries: ["...", "..."] ver' };
|
|
90
|
+
if (signal && signal.aborted) return { ok: false, error: 'iptal edildi', queries };
|
|
91
|
+
|
|
92
|
+
const maxResults = Math.max(5, Math.min(25, Number(a.max_results) || 16));
|
|
93
|
+
const readTop = Math.max(0, Math.min(6, Number(a.read_top != null ? a.read_top : (a.readTop != null ? a.readTop : 3))));
|
|
94
|
+
const search = deps && typeof deps.search === 'function' ? deps.search : null;
|
|
95
|
+
if (!search) return { ok: false, error: 'arama altyapısı yok', queries };
|
|
96
|
+
|
|
97
|
+
/* 1) paralel arama — bir sorgu çökse diğerleri devam eder */
|
|
98
|
+
const lists = await Promise.all(
|
|
99
|
+
queries.map((q) =>
|
|
100
|
+
Promise.resolve()
|
|
101
|
+
.then(() => search(q))
|
|
102
|
+
.then(normalizeRows)
|
|
103
|
+
.catch(() => [])
|
|
104
|
+
)
|
|
105
|
+
);
|
|
106
|
+
const results = mergeResults(lists, maxResults);
|
|
107
|
+
|
|
108
|
+
/* 2) ilk N sonucu gizli tarayıcıda açıp oku (ikili dosyaları atla) */
|
|
109
|
+
let pages = [];
|
|
110
|
+
if (readTop && results.length && deps && typeof deps.readPage === 'function') {
|
|
111
|
+
const targets = results.filter((r) => !SKIP_FILE_RE.test(r.url)).slice(0, readTop);
|
|
112
|
+
if (targets.length) {
|
|
113
|
+
pages = (await mapLimit(targets, 2, (r) => deps.readPage(r.url))).filter(Boolean);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const anyPage = pages.some((p) => p && p.ok);
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
ok: !!(results.length || anyPage),
|
|
120
|
+
queries,
|
|
121
|
+
results,
|
|
122
|
+
...(pages.length ? { pages } : {}),
|
|
123
|
+
...(results.length || anyPage
|
|
124
|
+
? {
|
|
125
|
+
note: anyPage
|
|
126
|
+
? 'sayfalar gizli tarayıcıda açılıp okundu — excerpt\u2019ler pages içinde; bir sayfayı kullanıcıya da göstermek istersen browser_open ile panelde aç'
|
|
127
|
+
: 'sonuç listesi hazır — tam metin okuma kapalı/başarısız; URL\u2019leri http_fetch ile okuyabilirsin',
|
|
128
|
+
}
|
|
129
|
+
: { error: 'tüm motorlar boş döndü — sorguları farklı ifade etmeyi dene' }),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
module.exports = { deepSearch, mergeResults, normalizeRows, normKey, mapLimit, SKIP_FILE_RE };
|