beast-agent 0.27.0 → 0.28.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/package.json +1 -1
- package/src/agent/engine.js +91 -53
- package/src/agent/obscura.js +245 -0
- package/src/agent/skills.js +14 -10
- package/src/agent/tools.js +105 -51
- package/src/main.js +47 -22
- package/src/preload.js +5 -3
- package/src/renderer/i18n.js +46 -24
- package/src/renderer/renderer.js +106 -38
- package/src/renderer/style.css +11 -0
- package/tests/bg-jobs.test.js +71 -3
- package/tests/obscura.test.js +124 -0
package/package.json
CHANGED
package/src/agent/engine.js
CHANGED
|
@@ -1173,6 +1173,8 @@ class Engine {
|
|
|
1173
1173
|
try {
|
|
1174
1174
|
const c = this.ctrls.get(id);
|
|
1175
1175
|
if (c) {
|
|
1176
|
+
this._abortReasons = this._abortReasons || new Map();
|
|
1177
|
+
this._abortReasons.set(String(id), 'oturum silindiği için tur iptal edildi');
|
|
1176
1178
|
c.abort();
|
|
1177
1179
|
this.ctrls.delete(id);
|
|
1178
1180
|
}
|
|
@@ -1182,6 +1184,7 @@ class Engine {
|
|
|
1182
1184
|
const j = this._bgJobs.get(String(id));
|
|
1183
1185
|
j.revive = false;
|
|
1184
1186
|
j.status = 'aborted';
|
|
1187
|
+
j.error = j.error || 'oturum silindi — paralel ajan iptal edildi';
|
|
1185
1188
|
this._clearKicks(id);
|
|
1186
1189
|
}
|
|
1187
1190
|
fs.unlinkSync(this._file(String(id)));
|
|
@@ -1316,7 +1319,7 @@ class Engine {
|
|
|
1316
1319
|
'Tarayıcı eylemlerinin yanıtındaki recent günlüğü ve navigated bilgisini takip et; eylem yanıtları zaten taze snapshot içerir — refler tutarsız görünürse yeni snapshot al.',
|
|
1317
1320
|
'CONUŞMA ODAĞI SENDE KALSIN: kullanıcı seninle konuşurken iş çıkmışsa — uzun da olsa UFACIK da (tek komutluk dizin listesi, tek dosya okuma, tek arama…) — run_background ile PARALEL ajana devret; ana sohbet hiçbir işi beklemez; bittiğinde özet otomatik düşer.',
|
|
1318
1321
|
'Python işleri için python_run kullan: küçük betikler inline code ile; tekrarlayan işler %APPDATA%\\beast\\scripts klasöründeki dosyalarla (ör. news.py = RSS haber toplayıcı: args ["--limit","8","--json"]). Python kurulu olmasa bile ilk çağrıda taşınabilir gömülü runtime otomatik iner.',
|
|
1319
|
-
|
|
1322
|
+
'PYTHON DURUMU: makinede sistem Python\'u görünmese bile ŞAŞIRMA ve "python yok" DEME — python_run aracı kendi taşınabilir runtime\'ını (%APPDATA%\\beast\\py\\python.exe) otomatik indirir/kullanır ve bu klasör run_command PATH\'inde önceliklidir; yani run_command içinde de `python` çalışır. Ham Google/Bing scrape yerine önce web_search aracını kullan (zaten Obscura stealth tarayıcı + TinyFish + Python çoklu-motor destekli), script gerektiğinde python_run yaz.',
|
|
1320
1323
|
'PDF ÇIKTI KURALI: PDF üretirken pip\u2019ten pdf paketi (fpdf, fpdf2, markdown-pdf, weasyprint, reportlab vb.) KURMA/KULLANMA — bunlar Türkçe karakterleri bozar. Doğru kit Node tarafında ZATEN kurulu: `pdf-lib` + `@pdf-lib/fontkit` (Türkçe font gömme) ve `pdfkit`. python_run ile DEĞİL; write_file ile .js script yazıp run_command ile `node script.js` çalıştır. md→pdf çevirici YOKTUR ve kurulmaz: kullanıcıya rapor/özet/belge çıktısı vereceksen .md dosyası gönderme — aynı içeriği DOĞRUDAN pdf-lib/pdfkit ile PDF olarak üret ve send_file ile o PDF\u2019i gönder. Ayrıntılı örnekler: pdf skill\u2019i (SKILL.md).',
|
|
1321
1324
|
'Eski bir hafıza kaydına ihtiyacın olursa memory_search ile ara; kalıcı bilgi/birikim için kb_search kullan, yeni bilgi öğrenirsen kb_add ile kaynak belirt.',
|
|
1322
1325
|
'Bir oturumda 3+ kez memory_write yaptıysan iş bitince memory_hygiene çağır (duplike/eskime temizliği).',
|
|
@@ -1335,7 +1338,8 @@ class Engine {
|
|
|
1335
1338
|
'Görev tanımı kendi başına YETERLİ olsun: tüm bağlam, dosya/URL yolları, kısıtlar ve beklenen çıktı görev metnine yazılır — paralel ajan senin bağlamını göremez.',
|
|
1336
1339
|
'Birden fazla bağımsız iş varsa hepsini aynı anda ayrı run_background çağrılarıyla devret (paralel çalışsınlar).',
|
|
1337
1340
|
'HIZ FAN-OUT: aynı işin farklı adımlarını run_background_many ile TEK SEFERDE paralel aç (örnek: haber taraması → TR / Dünya / Ekonomi ayrı ajan; araştırma+veri çekme+analiz ayrı ajan). Hepsi bitince sana TEK birleşik rapor düşer — hız dramatik artar. Bağımsız 2+ adım görürsen bunu tercih et.',
|
|
1338
|
-
'Takip araçları: tasks_list (genel tablo), task_status (tek ajanın canlı dökümü), task_cancel (iptal). Kullanıcı ilerleme sorarsa buradan raporla.',
|
|
1341
|
+
'Takip araçları: tasks_list (genel tablo), task_status (tek ajanın canlı dökümü), task_cancel (iptal — reason ZORUNLU). Kullanıcı ilerleme sorarsa buradan raporla.',
|
|
1342
|
+
'İPTAL DİSİPLİNİ: bir paralel ajanı task_cancel ile iptal edersen ya da bir işi yarıda bırakırsan, kullanıcıya TEK CÜMLEyle MUTLAKA SEBEBİNİ yaz (neden iptal ettin, ne kayboldu, yerine ne yapacaksın). Sebepsiz iptal YASAK.',
|
|
1339
1343
|
'Bilgi sorularını, sohbeti, planlamayı, hafıza/hatırlatıcı/izleyici kurulumunu YİNE SEN yaparsın — bunlar "iş" değildir.',
|
|
1340
1344
|
].join('\n')
|
|
1341
1345
|
);
|
|
@@ -1847,7 +1851,7 @@ class Engine {
|
|
|
1847
1851
|
if (j.status === 'running' || j.status === 'queued') {
|
|
1848
1852
|
j.status = 'aborted';
|
|
1849
1853
|
j.endedAt = new Date().toISOString();
|
|
1850
|
-
j.error = String(j.error || '') || 'uygulama kapatıldı';
|
|
1854
|
+
j.error = String(j.error || '') || 'uygulama kapatıldı — paralel ajan yarıda kesildi';
|
|
1851
1855
|
migrated = true;
|
|
1852
1856
|
}
|
|
1853
1857
|
j._persisted = true;
|
|
@@ -1940,9 +1944,18 @@ class Engine {
|
|
|
1940
1944
|
this._pendingReports = this._pendingReports || [];
|
|
1941
1945
|
this._pendingReports.push({ parentId: job.parentId, text });
|
|
1942
1946
|
this.flushPendingReports(job.parentId);
|
|
1943
|
-
} else if (status === 'aborted'
|
|
1944
|
-
/*
|
|
1945
|
-
|
|
1947
|
+
} else if (status === 'aborted') {
|
|
1948
|
+
/* İPTAL SEBEBİ ZORUNLU: sebebi kayda yaz ve üst sohbete/gruba bildir */
|
|
1949
|
+
const why = String(errorMsg || job.error || 'sebep belirtilmedi').slice(0, 300);
|
|
1950
|
+
if (grouped) {
|
|
1951
|
+
/* iptal edilen üye gruptan düşmüş sayılır — birleşik raporda SEBEPİYLE görünür */
|
|
1952
|
+
this._bgGroupRecord(job.title, 'aborted', 'İPTAL: ' + why);
|
|
1953
|
+
} else {
|
|
1954
|
+
const text = `[ARKA PLAN İPTAL: ${job.title}]\nSebep: ${why}`;
|
|
1955
|
+
this._pendingReports = this._pendingReports || [];
|
|
1956
|
+
this._pendingReports.push({ parentId: job.parentId, text });
|
|
1957
|
+
this.flushPendingReports(job.parentId);
|
|
1958
|
+
}
|
|
1946
1959
|
}
|
|
1947
1960
|
}
|
|
1948
1961
|
|
|
@@ -2432,8 +2445,21 @@ class Engine {
|
|
|
2432
2445
|
const aborted = e && (e.name === 'AbortError' || ctrl.signal.aborted);
|
|
2433
2446
|
if (aborted) {
|
|
2434
2447
|
this._clearCrash(); // kullanıcı durdurdu — kurtarma yok
|
|
2435
|
-
|
|
2436
|
-
|
|
2448
|
+
/* öz-kurtarma/superyorizon kick'i bekliyorsa bu tur BİTİŞ değildir —
|
|
2449
|
+
işi aborted KAPATMA, kick mesajı yeni turu açar */
|
|
2450
|
+
const kickJob = this._bgJobs && this._bgJobs.get(sid);
|
|
2451
|
+
if (kickJob && kickJob.revive === true) {
|
|
2452
|
+
emit({ type: 'done', aborted: true });
|
|
2453
|
+
} else {
|
|
2454
|
+
/* İPTAL SEBEBİ ZORUNLU: interrupt() tarafından kaydedilir */
|
|
2455
|
+
const why =
|
|
2456
|
+
(this._abortReasons && this._abortReasons.get(sid)) ||
|
|
2457
|
+
(kickJob && kickJob.error) ||
|
|
2458
|
+
'kullanıcı talebiyle iptal edildi';
|
|
2459
|
+
if (this._abortReasons) this._abortReasons.delete(sid);
|
|
2460
|
+
emit({ type: 'done', aborted: true, reason: why });
|
|
2461
|
+
this._bgFinish(sid, 'aborted', why);
|
|
2462
|
+
}
|
|
2437
2463
|
} else {
|
|
2438
2464
|
// FALLOUT: tüm zincir tükendi — durumu kaydet, açılışta kaldığı yerden devam edilir
|
|
2439
2465
|
this._saveCrash(sid, e, 'chain-exhausted');
|
|
@@ -2676,7 +2702,7 @@ class Engine {
|
|
|
2676
2702
|
if (!this.ctrls.has(pairId)) break;
|
|
2677
2703
|
}
|
|
2678
2704
|
if (this.ctrls.has(pairId)) {
|
|
2679
|
-
try { this.interrupt(pairId); } catch {}
|
|
2705
|
+
try { this.interrupt(pairId, 'bot DM zaman aşımına uğradı (120 sn) — tur kesildi'); } catch {}
|
|
2680
2706
|
return { ok: false, error: 'hedef bot zaman aşımına uğradı (120 sn)' };
|
|
2681
2707
|
}
|
|
2682
2708
|
const after = this.cache.get(pairId) || pair;
|
|
@@ -2790,39 +2816,19 @@ class Engine {
|
|
|
2790
2816
|
}
|
|
2791
2817
|
}
|
|
2792
2818
|
|
|
2793
|
-
/* web_search zinciri — web_search VE deep_search aynı zinciri kullanır
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
4)
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
async _webSearchChain(q, n, sessionId, signal) {
|
|
2801
|
-
let r = null;
|
|
2802
|
-
let banned = false;
|
|
2819
|
+
/* web_search zinciri — web_search VE deep_search aynı zinciri kullanır.
|
|
2820
|
+
Sıra + aç/kapa Ayarlar → Web Arama'dan değiştirilir (tools.searchChainWeb):
|
|
2821
|
+
varsayılan: 1) dahili tarayıcı (DİREK GOOGLE) 2) Obscura (stealth headless
|
|
2822
|
+
→ DuckDuckGo; otomatik kurulur, varsayılan AKTİF) 3) TinyFish (anahtar
|
|
2823
|
+
girildiyse) 4) python çoklu-motor. Tarayıcı CAPTCHA/trafik verirse 10 dk
|
|
2824
|
+
atlanır — sıradaki motor hemen devreye girer. */
|
|
2825
|
+
_webSearchChain(q, n, sessionId, signal) {
|
|
2803
2826
|
const browserUsable = this.browser && typeof this.browser.search === 'function';
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
this._webSearchBrowserBanUntil = Date.now() + 10 * 60 * 1000;
|
|
2810
|
-
r = null;
|
|
2811
|
-
banned = true;
|
|
2812
|
-
}
|
|
2813
|
-
}
|
|
2814
|
-
if (!r || !r.ok || !(r.results || []).length) {
|
|
2815
|
-
try { r = await tools.tinyfishSearch(q, n, signal); } catch {}
|
|
2816
|
-
}
|
|
2817
|
-
if (!r || !r.ok || !(r.results || []).length) {
|
|
2818
|
-
try {
|
|
2819
|
-
r = JSON.parse(await tools.exec('web_search', { query: q, max_results: n }, { cwd: this._sessionWorkspace(sessionId), signal }));
|
|
2820
|
-
} catch {}
|
|
2821
|
-
}
|
|
2822
|
-
const out = r || { ok: false, error: 'web arama başarısız — tüm motorlar boş döndü' };
|
|
2823
|
-
if (out && out.ok && banned) out.note = 'tarayıcı araması 10 dk askıda (CAPTCHA/trafik) — TinyFish/alternatif zincir kullanıldı';
|
|
2824
|
-
return out;
|
|
2825
|
-
}
|
|
2827
|
+
return tools.searchChainWeb(q, n, {
|
|
2828
|
+
signal,
|
|
2829
|
+
browser: browserUsable ? () => this.browser.search(q, signal, { sessionId }) : null,
|
|
2830
|
+
});
|
|
2831
|
+
}
|
|
2826
2832
|
|
|
2827
2833
|
/* Dahili OCR aracı: görüntüden metin çıkarır (görsel desteklemeyen modeller için).
|
|
2828
2834
|
Kaynak: 'browser' (dahili tarayıcı görüntüsü, varsayılan) | 'screen' (masaüstü) | dosya yolu. */
|
|
@@ -3051,8 +3057,11 @@ class Engine {
|
|
|
3051
3057
|
}
|
|
3052
3058
|
if (name === 'task_cancel') {
|
|
3053
3059
|
const id = String(args.id || '');
|
|
3054
|
-
const
|
|
3055
|
-
|
|
3060
|
+
const why = String(args.reason || '').trim() || 'ana ajan (CEO) bu ajanı task_cancel ile iptal etti';
|
|
3061
|
+
const ok = this.interrupt(id, why);
|
|
3062
|
+
return JSON.stringify(ok
|
|
3063
|
+
? { ok: true, id, reason: why, note: 'Ajan iptal edildi — kullanıcıya MUTLAKA iptal sebebini tek cümleyle açıkla: ' + why }
|
|
3064
|
+
: { ok: false, error: 'çalışan ajan yok: ' + id });
|
|
3056
3065
|
}
|
|
3057
3066
|
if (name === 'set_reminder') {
|
|
3058
3067
|
if (!this.reminders || typeof this.reminders.add !== 'function') {
|
|
@@ -3174,10 +3183,8 @@ class Engine {
|
|
|
3174
3183
|
}
|
|
3175
3184
|
if (name === 'deep_search') {
|
|
3176
3185
|
/* agentic derin araştırma: çoklu sorgu + gizli tarayıcıda sayfa okuma.
|
|
3177
|
-
ARAMA zinciri web_search ile BİREBİR AYNI —
|
|
3178
|
-
|
|
3179
|
-
boş dönünce "tüm motorlar boş döndü" hatası veriyordu. Artık önce
|
|
3180
|
-
DAHİLİ TARAYICI (Google) denenir, sonra TinyFish → python → Exa. */
|
|
3186
|
+
ARAMA zinciri web_search ile BİREBİR AYNI — sıralı zincir
|
|
3187
|
+
(tarayıcı → Obscura → TinyFish → python; Ayarlar'dan değiştirilir). */
|
|
3181
3188
|
try {
|
|
3182
3189
|
emitSafe(this, sessionId, { type: 'status', status: 'derin araştırma: çoklu sorgu + gizli sayfa okuma' });
|
|
3183
3190
|
} catch {}
|
|
@@ -3242,9 +3249,34 @@ class Engine {
|
|
|
3242
3249
|
}
|
|
3243
3250
|
}
|
|
3244
3251
|
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3252
|
+
/* İptal SEBEBİ zorunlu: abort edilen her tur/_bgFinish bu haritadan sebebini
|
|
3253
|
+
okur; ajan neden durdurulduğunu kullanıcıya MUTLAKA bildirir. */
|
|
3254
|
+
_abortReason(reason) {
|
|
3255
|
+
const r = String(reason || '').trim();
|
|
3256
|
+
return (r || 'sebep belirtilmedi — iptal talebi').slice(0, 400);
|
|
3257
|
+
}
|
|
3258
|
+
|
|
3259
|
+
interrupt(sessionId, reason) {
|
|
3260
|
+
const sid = String(sessionId || '');
|
|
3261
|
+
const why = this._abortReason(reason);
|
|
3262
|
+
const c = this.ctrls.get(sid);
|
|
3263
|
+
if (!c) {
|
|
3264
|
+
/* koşan tur yok — kuyrukta bekleyen / öz-kurtarma kick'i bekleyen
|
|
3265
|
+
paralel ajanı da SEBEPİYLE kes */
|
|
3266
|
+
const j = this._bgJobs && this._bgJobs.get(sid);
|
|
3267
|
+
if (j && (j.status === 'queued' || j.revive === true)) {
|
|
3268
|
+
j.status = 'aborted';
|
|
3269
|
+
j.revive = false;
|
|
3270
|
+
j.endedAt = nowIso();
|
|
3271
|
+
j.error = why;
|
|
3272
|
+
this._clearKicks(sid);
|
|
3273
|
+
this._bgEmit();
|
|
3274
|
+
return true;
|
|
3275
|
+
}
|
|
3276
|
+
return false;
|
|
3277
|
+
}
|
|
3278
|
+
this._abortReasons = this._abortReasons || new Map();
|
|
3279
|
+
this._abortReasons.set(sid, why);
|
|
3248
3280
|
c.abort();
|
|
3249
3281
|
return true;
|
|
3250
3282
|
}
|
|
@@ -3254,19 +3286,22 @@ class Engine {
|
|
|
3254
3286
|
main katmanında BAĞIMSIZ yaşar — /stop onlara dokunmaz. */
|
|
3255
3287
|
stopAll() {
|
|
3256
3288
|
let aborted = 0;
|
|
3289
|
+
const why = '/stop: kullanıcı tüm ajanları ve turları durdurdu';
|
|
3257
3290
|
/* fan-out gruplarını kapat — yarım grup artık birleşik rapor beklemesin */
|
|
3258
3291
|
if (this._bgGroups) {
|
|
3259
3292
|
for (const g of this._bgGroups.values()) g.dead = true;
|
|
3260
3293
|
}
|
|
3261
3294
|
if (this._bgPendingStart) this._bgPendingStart.clear();
|
|
3295
|
+
this._abortReasons = this._abortReasons || new Map();
|
|
3262
3296
|
for (const [sid, c] of this.ctrls) {
|
|
3263
|
-
try { c.abort(); aborted++; } catch {}
|
|
3297
|
+
try { this._abortReasons.set(sid, why); c.abort(); aborted++; } catch {}
|
|
3264
3298
|
/* abort sonrası _run finally bloğu temizler; paralel ajan kaydını biz işaretleyelim */
|
|
3265
3299
|
if (this._bgJobs.has(sid)) {
|
|
3266
3300
|
const job = this._bgJobs.get(sid);
|
|
3267
3301
|
if (job.status === 'running' || job.status === 'queued') {
|
|
3268
3302
|
job.status = 'aborted';
|
|
3269
3303
|
job.endedAt = nowIso();
|
|
3304
|
+
job.error = job.error || why;
|
|
3270
3305
|
}
|
|
3271
3306
|
job.revive = false; // bekleyen öz-kurtarma gönderimini de öldür
|
|
3272
3307
|
this._clearKicks(sid);
|
|
@@ -3277,6 +3312,7 @@ class Engine {
|
|
|
3277
3312
|
if (j.status === 'queued') {
|
|
3278
3313
|
j.status = 'aborted';
|
|
3279
3314
|
j.endedAt = nowIso();
|
|
3315
|
+
j.error = j.error || why;
|
|
3280
3316
|
aborted++;
|
|
3281
3317
|
}
|
|
3282
3318
|
}
|
|
@@ -3648,13 +3684,15 @@ const TOOLS = [
|
|
|
3648
3684
|
type: 'function',
|
|
3649
3685
|
function: {
|
|
3650
3686
|
name: 'task_cancel',
|
|
3651
|
-
description:
|
|
3687
|
+
description:
|
|
3688
|
+
'Cancel a running background agent by id (from tasks_list). ALWAYS pass reason: a short sentence explaining WHY you are cancelling — it is recorded on the job and you must report it to the user.',
|
|
3652
3689
|
parameters: {
|
|
3653
3690
|
type: 'object',
|
|
3654
3691
|
properties: {
|
|
3655
3692
|
id: { type: 'string' },
|
|
3693
|
+
reason: { type: 'string', description: 'REQUIRED: why you are cancelling this agent (one short sentence)' },
|
|
3656
3694
|
},
|
|
3657
|
-
required: ['id'],
|
|
3695
|
+
required: ['id', 'reason'],
|
|
3658
3696
|
},
|
|
3659
3697
|
},
|
|
3660
3698
|
},
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/* ---------- Obscura entegrasyonu ----------
|
|
4
|
+
Obscura (github.com/h4ckf0r0day/obscura): Rust tabanlı stealth headless
|
|
5
|
+
tarayıcı — anti-detect + V8 JS + gerçek rendering, Chromium'suz.
|
|
6
|
+
Beast kullanımı:
|
|
7
|
+
1) Kurulum: releases'taki Windows stealth zip'i %APPDATA%\beast\obscura'ya
|
|
8
|
+
açılır (obscura.exe + obscura-worker.exe). İlk açılışta OTOMATİK kurulur.
|
|
9
|
+
2) Arama: `obscura fetch https://html.duckduckgo.com/html/?q=...` çıktısı
|
|
10
|
+
DDG sonuç ayrıştırıcısıyla results'a döner — web_search zincirinin
|
|
11
|
+
sıralı bir motoru (Ayarlar → Web Arama'dan sıra değiştirilebilir). */
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const os = require('os');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const https = require('https');
|
|
17
|
+
const { execFile, spawn } = require('child_process');
|
|
18
|
+
|
|
19
|
+
const RELEASE_BASE = 'https://github.com/h4ckf0r0day/obscura/releases/latest/download/';
|
|
20
|
+
const WINDOWS_ZIP = 'obscura-x86_64-windows-stealth.zip';
|
|
21
|
+
const MAX_ZIP_BYTES = 250 * 1024 * 1024;
|
|
22
|
+
const FETCH_TIMEOUT_MS = 25000;
|
|
23
|
+
|
|
24
|
+
function obscuraDir() {
|
|
25
|
+
const base = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming');
|
|
26
|
+
return path.join(base, 'beast', 'obscura');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function obscuraExe() {
|
|
30
|
+
return path.join(obscuraDir(), 'obscura.exe');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function obscuraInstalled() {
|
|
34
|
+
try { return fs.existsSync(obscuraExe()); } catch { return false; }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* kurulan varyant işareti: stealth zip'te --stealth bayrağı geçerli */
|
|
38
|
+
function markVariant(v) {
|
|
39
|
+
try { fs.writeFileSync(path.join(obscuraDir(), 'variant.txt'), String(v || 'stealth') + '\n', 'utf8'); } catch {}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function readVariant() {
|
|
43
|
+
try { return String(fs.readFileSync(path.join(obscuraDir(), 'variant.txt'), 'utf8')).trim() || 'stealth'; } catch { return 'stealth'; }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* bina sağ mı: --help 10 sn içinde cevap veriyor mu (oturum başına tek deneme) */
|
|
47
|
+
let _okProbe = null;
|
|
48
|
+
function obscuraOk() {
|
|
49
|
+
if (!obscuraInstalled()) return Promise.resolve(false);
|
|
50
|
+
if (_okProbe) return _okProbe;
|
|
51
|
+
_okProbe = new Promise((resolve) => {
|
|
52
|
+
try {
|
|
53
|
+
execFile(obscuraExe(), ['--help'], { timeout: 10000, windowsHide: true }, (err) => resolve(!err));
|
|
54
|
+
} catch {
|
|
55
|
+
resolve(false);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
return _okProbe;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function httpsDownload(url, redirectsLeft = 5, signal) {
|
|
62
|
+
return new Promise((resolve, reject) => {
|
|
63
|
+
const req = https.get(url, { headers: { 'User-Agent': 'BeastAgent/1.0 (+obscura bootstrap)' } }, (res) => {
|
|
64
|
+
if ([301, 302, 303, 307, 308].includes(res.statusCode) && res.headers.location && redirectsLeft > 0) {
|
|
65
|
+
res.resume();
|
|
66
|
+
return resolve(httpsDownload(new URL(res.headers.location, url).toString(), redirectsLeft - 1, signal));
|
|
67
|
+
}
|
|
68
|
+
if (res.statusCode !== 200) {
|
|
69
|
+
res.resume();
|
|
70
|
+
return reject(new Error(`indirme başarısız: HTTP ${res.statusCode}`));
|
|
71
|
+
}
|
|
72
|
+
const chunks = [];
|
|
73
|
+
let size = 0;
|
|
74
|
+
res.on('data', (c) => {
|
|
75
|
+
size += c.length;
|
|
76
|
+
if (size > MAX_ZIP_BYTES) {
|
|
77
|
+
req.destroy(new Error('dosya çok büyük'));
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
chunks.push(c);
|
|
81
|
+
});
|
|
82
|
+
res.on('end', () => resolve(Buffer.concat(chunks)));
|
|
83
|
+
res.on('error', reject);
|
|
84
|
+
});
|
|
85
|
+
req.on('error', reject);
|
|
86
|
+
if (signal) {
|
|
87
|
+
if (signal.aborted) return reject(new Error('iptal'));
|
|
88
|
+
signal.addEventListener('abort', () => req.destroy(new Error('iptal')), { once: true });
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function psExpand(zipPath, destDir) {
|
|
94
|
+
return new Promise((resolve) => {
|
|
95
|
+
const cmd = `Expand-Archive -LiteralPath "${zipPath}" -DestinationPath "${destDir}" -Force`;
|
|
96
|
+
const child = spawn(
|
|
97
|
+
'powershell.exe',
|
|
98
|
+
['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', cmd],
|
|
99
|
+
{ windowsHide: true }
|
|
100
|
+
);
|
|
101
|
+
let err = '';
|
|
102
|
+
const finish = (ok) => {
|
|
103
|
+
clearTimeout(timer);
|
|
104
|
+
resolve(ok);
|
|
105
|
+
};
|
|
106
|
+
const timer = setTimeout(() => {
|
|
107
|
+
try { spawn('taskkill', ['/pid', String(child.pid), '/T', '/F'], { windowsHide: true }); } catch {}
|
|
108
|
+
finish(false);
|
|
109
|
+
}, 180000);
|
|
110
|
+
child.stderr.setEncoding('utf8');
|
|
111
|
+
child.stderr.on('data', (d) => { err += d; });
|
|
112
|
+
child.on('error', () => finish(false));
|
|
113
|
+
child.on('close', (code) => finish(code === 0 && !/Expand-Archive :/i.test(err)));
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/* Obscura'yı kur / güncelle (yeniden indirip üzerine yazar) */
|
|
118
|
+
async function installObscura(signal) {
|
|
119
|
+
const dest = obscuraDir();
|
|
120
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
121
|
+
const zipPath = path.join(os.tmpdir(), 'beast-obscura.zip');
|
|
122
|
+
const buf = await httpsDownload(RELEASE_BASE + WINDOWS_ZIP, 5, signal);
|
|
123
|
+
fs.writeFileSync(zipPath, buf);
|
|
124
|
+
const ok = await psExpand(zipPath, dest);
|
|
125
|
+
try { fs.unlinkSync(zipPath); } catch {}
|
|
126
|
+
if (!ok || !obscuraInstalled()) {
|
|
127
|
+
return { ok: false, error: 'obscura kurulumu başarısız (zip açılamadı)' };
|
|
128
|
+
}
|
|
129
|
+
markVariant('stealth');
|
|
130
|
+
_okProbe = null; /* yeniden doğrula */
|
|
131
|
+
const good = await obscuraOk();
|
|
132
|
+
return { ok: good, dir: dest, error: good ? null : 'obscura.exe doğrulanamadı' };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* obscura fetch — sayfa HTML'i (abort ile süreç kesilir) */
|
|
136
|
+
function obscuraFetchHtml(url, signal, timeoutMs = FETCH_TIMEOUT_MS) {
|
|
137
|
+
return new Promise((resolve, reject) => {
|
|
138
|
+
const args = ['fetch', url, '--dump', 'html', '--quiet', '--timeout', '20'];
|
|
139
|
+
if (readVariant() === 'stealth') args.push('--stealth');
|
|
140
|
+
let settled = false;
|
|
141
|
+
const finish = (err, html) => {
|
|
142
|
+
if (settled) return;
|
|
143
|
+
settled = true;
|
|
144
|
+
clearTimeout(timer);
|
|
145
|
+
if (signal) signal.removeEventListener('abort', onAbort);
|
|
146
|
+
err ? reject(err) : resolve(String(html || ''));
|
|
147
|
+
};
|
|
148
|
+
let child = null;
|
|
149
|
+
const onAbort = () => {
|
|
150
|
+
try {
|
|
151
|
+
if (child && child.pid) spawn('taskkill', ['/pid', String(child.pid), '/T', '/F'], { windowsHide: true });
|
|
152
|
+
} catch {}
|
|
153
|
+
finish(new Error('obscura fetch iptal edildi'));
|
|
154
|
+
};
|
|
155
|
+
const timer = setTimeout(() => {
|
|
156
|
+
try {
|
|
157
|
+
if (child && child.pid) spawn('taskkill', ['/pid', String(child.pid), '/T', '/F'], { windowsHide: true });
|
|
158
|
+
} catch {}
|
|
159
|
+
finish(new Error('obscura fetch zaman aşımı'));
|
|
160
|
+
}, timeoutMs);
|
|
161
|
+
if (signal) {
|
|
162
|
+
if (signal.aborted) return finish(new Error('iptal'));
|
|
163
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
164
|
+
}
|
|
165
|
+
try {
|
|
166
|
+
child = execFile(
|
|
167
|
+
obscuraExe(),
|
|
168
|
+
args,
|
|
169
|
+
{ timeout: timeoutMs, windowsHide: true, maxBuffer: 8 * 1024 * 1024 },
|
|
170
|
+
(err, stdout) => finish(err, stdout)
|
|
171
|
+
);
|
|
172
|
+
} catch (e) {
|
|
173
|
+
finish(e);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/* lite.duckduckgo sonuç ayrıştırıcı (tablo düzeni, result-link çıpaları) */
|
|
179
|
+
function parseDdgLite(html, limit = 8) {
|
|
180
|
+
const out = [];
|
|
181
|
+
const re = /<a\s+[^>]*class="[^"]*result-link[^"]*"[^>]*href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/g;
|
|
182
|
+
let m;
|
|
183
|
+
while ((m = re.exec(String(html || ''))) && out.length < limit) {
|
|
184
|
+
let href = m[1] || '';
|
|
185
|
+
try {
|
|
186
|
+
const u = new URL(href, 'https://duckduckgo.com');
|
|
187
|
+
const uddg = u.searchParams.get('uddg');
|
|
188
|
+
href = uddg ? decodeURIComponent(uddg) : u.toString();
|
|
189
|
+
} catch {}
|
|
190
|
+
const title = String(m[2] || '').replace(/<[^>]+>/g, '').trim();
|
|
191
|
+
if (!title || !/^https?:\/\//i.test(href)) continue;
|
|
192
|
+
if (out.some((r) => r.url === href)) continue;
|
|
193
|
+
out.push({ title, url: href, snippet: '' });
|
|
194
|
+
}
|
|
195
|
+
return out;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/* Obscura arama motoru: DDG html (önce) → DDG lite (yedek), stealth ile.
|
|
199
|
+
Döndüremiyorsa null — zincir sıradaki motora geçer. */
|
|
200
|
+
async function obscuraSearch(query, { maxResults = 8, signal } = {}) {
|
|
201
|
+
const q = String(query || '').trim().slice(0, 400);
|
|
202
|
+
if (!q || !obscuraInstalled()) return null;
|
|
203
|
+
const cap = Math.max(1, Math.min(12, Number(maxResults) || 8));
|
|
204
|
+
const targets = [
|
|
205
|
+
'https://html.duckduckgo.com/html/?q=' + encodeURIComponent(q),
|
|
206
|
+
'https://lite.duckduckgo.com/lite/?q=' + encodeURIComponent(q),
|
|
207
|
+
];
|
|
208
|
+
for (const url of targets) {
|
|
209
|
+
let html = '';
|
|
210
|
+
try {
|
|
211
|
+
html = await obscuraFetchHtml(url, signal);
|
|
212
|
+
} catch {
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
if (!html) continue;
|
|
216
|
+
let results = [];
|
|
217
|
+
const { parseDdgResults } = require('./tools'); /* döngüsel require — çağrı anında */
|
|
218
|
+
if (/result__a/.test(html)) results = parseDdgResults(html, cap);
|
|
219
|
+
if (!results.length) results = parseDdgLite(html, cap);
|
|
220
|
+
if (results.length) {
|
|
221
|
+
return {
|
|
222
|
+
ok: true,
|
|
223
|
+
engine: 'obscura',
|
|
224
|
+
query: q,
|
|
225
|
+
count: results.length,
|
|
226
|
+
results: results.map((r) => ({ ...r, engine: 'obscura' })),
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
module.exports = {
|
|
234
|
+
ENGINE: 'obscura',
|
|
235
|
+
RELEASE_BASE,
|
|
236
|
+
WINDOWS_ZIP,
|
|
237
|
+
obscuraDir,
|
|
238
|
+
obscuraExe,
|
|
239
|
+
obscuraInstalled,
|
|
240
|
+
obscuraOk,
|
|
241
|
+
installObscura,
|
|
242
|
+
obscuraFetchHtml,
|
|
243
|
+
obscuraSearch,
|
|
244
|
+
parseDdgLite,
|
|
245
|
+
};
|
package/src/agent/skills.js
CHANGED
|
@@ -164,19 +164,27 @@ Send-MailMessage -SmtpServer smtp.gmail.com -Port 587 -UseSsl -From 'adres@gmail
|
|
|
164
164
|
force: true,
|
|
165
165
|
body: `---
|
|
166
166
|
name: python-web-search
|
|
167
|
-
description: VARSAYILAN web arama zinciri — 1) dahili tarayıcı DİREK GOOGLE (gerçek Chromium, bot koruması yok; Google AI cevabi 'ai' alanında hazır gelir) 2) python çoklu-motor (ddgs / DDG+Bing+Mojeek, anahtarsız)
|
|
168
|
-
version: 1.
|
|
167
|
+
description: VARSAYILAN web arama zinciri — 1) dahili tarayıcı DİREK GOOGLE (gerçek Chromium, bot koruması yok; Google AI cevabi 'ai' alanında hazır gelir) 2) Obscura (stealth headless → DuckDuckGo; otomatik kurulur, varsayılan AKTİF) 3) TinyFish (anahtar varsa) 4) python çoklu-motor (ddgs / DDG+Bing+Mojeek, anahtarsız). Sıra Ayarlar → Web Arama'dan değiştirilir. Takılmama kuralları içerir.
|
|
168
|
+
version: 1.4.0
|
|
169
169
|
---
|
|
170
170
|
|
|
171
171
|
# Web Arama Zinciri (VARSAYILAN)
|
|
172
172
|
|
|
173
|
-
web_search aracı çağrıldığında zincir OTOMATİK çalışır — elle motor seçmen gerekmez:
|
|
173
|
+
web_search aracı çağrıldığında zincir OTOMATİK çalışır — elle motor seçmen gerekmez. Sıra Ayarlar → Web Arama sekmesinden değiştirilebilir:
|
|
174
174
|
|
|
175
175
|
## 1. Dahili tarayıcı (ilk tercih — Google AI)
|
|
176
176
|
|
|
177
|
-
Gerçek Chromium olduğu için Google'a bot koruması uygulamaz. Arama aep=1 ile açılır: Google'ın KENDİ AI cevabı yanıtın 'ai' alanında HAZIR gelir — "kimdir/nedir" sorularında önce onu kullan, kaynak linkleri de 'results' alanında gelir. Sağ panelde açılır, kullanıcı da görebilir.
|
|
177
|
+
Gerçek Chromium olduğu için Google'a bot koruması uygulamaz. Arama aep=1 ile açılır: Google'ın KENDİ AI cevabı yanıtın 'ai' alanında HAZIR gelir — "kimdir/nedir" sorularında önce onu kullan, kaynak linkleri de 'results' alanında gelir. Sağ panelde açılır, kullanıcı da görebilir. CAPTCHA/trafik uyarısı gelirse tarayıcı 10 dk atlanır, sıradaki motor devreye girer.
|
|
178
178
|
|
|
179
|
-
## 2.
|
|
179
|
+
## 2. Obscura (stealth headless — DuckDuckGo)
|
|
180
|
+
|
|
181
|
+
Rust tabanlı gizli tarayıcı: anti-detect parmak izi + V8 JS. Kurulu değilse uygulama açılışında OTOMATİK indirilir (%APPDATA%\\beast\\obscura). Varsayılan AKTİF — tarayıcı engellenirse bot korumasını aşarak DuckDuckGo'dan sonuç getirir.
|
|
182
|
+
|
|
183
|
+
## 3. TinyFish API (anahtar varsa)
|
|
184
|
+
|
|
185
|
+
Ayarlar → Web Arama'dan anahtar girildiyse kendi sırasında devreye girer; anahtar yoksa otomatik atlanır.
|
|
186
|
+
|
|
187
|
+
## 4. Python çoklu-motor
|
|
180
188
|
|
|
181
189
|
ddgs kütüphanesi (github.com/deedy5/ddgs — anti-bot korumalı metasearch) veya DuckDuckGo + Bing + Mojeek PARALEL — anahtar gerekmez. Gömülü Python runtime otomatik yönetilir.
|
|
182
190
|
|
|
@@ -187,10 +195,6 @@ web_search {"query": "sorgu", "max_results": 6}
|
|
|
187
195
|
- Çoklu sorgu gerekiyorsa: aynı turda birden ÇOK web_search çağrısını PARALEL ver — toplam süre tek arama kadar olur.
|
|
188
196
|
- Sonuç listesinden 1-2 URL'nin içeriğini okumak için http_fetch kullan.
|
|
189
197
|
|
|
190
|
-
## 3. Exa (son çare)
|
|
191
|
-
|
|
192
|
-
Yukarıdakiler boş dönerse ve Ayarlar → Web Arama'ya Exa anahtarı girildiyse devreye girer.
|
|
193
|
-
|
|
194
198
|
## Toplu / derin işler (python_run)
|
|
195
199
|
|
|
196
200
|
- Haber başlıkları: python_run script=news.py args ["--limit","8","--json"]
|
|
@@ -208,7 +212,7 @@ for q in ["sorgu 1", "sorgu 2", "sorgu 3"]:
|
|
|
208
212
|
|
|
209
213
|
- Bir bilgi 2-3 farklı sorguyla bulunamıyorsa PEŞİNİ BIRAK: bulduğun kısmi bilgiyle devam et, neyi bulamadığını raporda açıkça yaz.
|
|
210
214
|
- Kapalı/gizli içerik (Instagram gizli hesap, private profil, login arkası veri) ARANMAZ — bulunamayacağı belliyse hemen alternatif kaynağa/geçişe geç.
|
|
211
|
-
- DDG boş/engelli dönerse "DuckDuckGo kapandı" DEME: bot koruması devrededir (~1 saatte kendiliğinden açılır) ve zincir otomatik olarak Bing/Mojeek/tarayıcıya geçer — bu normal çalışmadır.
|
|
215
|
+
- DDG boş/engelli dönerse "DuckDuckGo kapandı" DEME: bot koruması devrededir (~1 saatte kendiliğinden açılır) ve zincir otomatik olarak Obscura/Bing/Mojeek/tarayıcıya geçer — bu normal çalışmadır.
|
|
212
216
|
- Araştırma hedefi: ~3 dakika, 3-5 kaynak. Derinleşme = zaman kaybı.
|
|
213
217
|
- Motor hata verirse bir kez farklı sorguyla dene, sonra elindekiyle raporu kapat.`,
|
|
214
218
|
},
|