beast-agent 0.26.5 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "beast-agent",
3
3
  "productName": "Beast Agent",
4
- "version": "0.26.5",
4
+ "version": "0.28.0",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
@@ -19,6 +19,9 @@ const SUB_MAX_TURNS = 8;
19
19
  const HISTORY_TOKEN_BUDGET = 18000;
20
20
  const TOOL_OUT_KEEP = 1200;
21
21
  const USER_MAX = 8000;
22
+ /* observe() ile enjekte edilen bağlam mesajlarının öneki — ardışık bağlam
23
+ mesajlarının tek mesajda birleşmesi bu işaretle anlaşılır */
24
+ const OBSERVE_MARK = '[BAĞLAM';
22
25
 
23
26
  /* Oturum notları (geçici hafıza): eski mesajlar bütçeden düşülmeden önce
24
27
  önemli noktalar çıkarılır, sistem promptuna küçük blok olarak girer. */
@@ -1170,6 +1173,8 @@ class Engine {
1170
1173
  try {
1171
1174
  const c = this.ctrls.get(id);
1172
1175
  if (c) {
1176
+ this._abortReasons = this._abortReasons || new Map();
1177
+ this._abortReasons.set(String(id), 'oturum silindiği için tur iptal edildi');
1173
1178
  c.abort();
1174
1179
  this.ctrls.delete(id);
1175
1180
  }
@@ -1179,6 +1184,7 @@ class Engine {
1179
1184
  const j = this._bgJobs.get(String(id));
1180
1185
  j.revive = false;
1181
1186
  j.status = 'aborted';
1187
+ j.error = j.error || 'oturum silindi — paralel ajan iptal edildi';
1182
1188
  this._clearKicks(id);
1183
1189
  }
1184
1190
  fs.unlinkSync(this._file(String(id)));
@@ -1313,7 +1319,7 @@ class Engine {
1313
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.',
1314
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.',
1315
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.',
1316
- '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 Python çoklu-motor + Exa destekli), script gerektiğinde python_run yaz.',
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.',
1317
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).',
1318
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.',
1319
1325
  'Bir oturumda 3+ kez memory_write yaptıysan iş bitince memory_hygiene çağır (duplike/eskime temizliği).',
@@ -1332,7 +1338,8 @@ class Engine {
1332
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.',
1333
1339
  'Birden fazla bağımsız iş varsa hepsini aynı anda ayrı run_background çağrılarıyla devret (paralel çalışsınlar).',
1334
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.',
1335
- '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.',
1336
1343
  'Bilgi sorularını, sohbeti, planlamayı, hafıza/hatırlatıcı/izleyici kurulumunu YİNE SEN yaparsın — bunlar "iş" değildir.',
1337
1344
  ].join('\n')
1338
1345
  );
@@ -1611,6 +1618,38 @@ class Engine {
1611
1618
  return true;
1612
1619
  }
1613
1620
 
1621
+ /* SESSİZ BAĞLAM ENJEKSİYONU: metin oturum geçmişine düşer ama _run()
1622
+ TETİKLENMEZ — ajan "görür", cevap ÜRETMEZ. WhatsApp gruplarında
1623
+ @mention moduyla kullanılır: grubun tüm konuşması bağlam olarak
1624
+ birikir, bot yalnız mention gelince konuşur. Ardışık bağlam
1625
+ mesajları işaret önekli tek user mesajında birleşir (bazı sağlayıcılar
1626
+ art arda user mesajını reddettiği için). */
1627
+ observe(sessionId, text) {
1628
+ const s = this._load(String(sessionId));
1629
+ if (!s) return false;
1630
+ const body = String(text || '').slice(0, USER_MAX);
1631
+ if (!body.trim()) return false;
1632
+ const lastMsg = s.messages[s.messages.length - 1];
1633
+ if (
1634
+ lastMsg && lastMsg.role === 'user' &&
1635
+ typeof lastMsg.content === 'string' &&
1636
+ lastMsg.content.startsWith(OBSERVE_MARK)
1637
+ ) {
1638
+ /* önceki bağlam mesajının devamı — tek mesajda birleştir */
1639
+ lastMsg.content = (lastMsg.content + '\n' + body).slice(0, USER_MAX);
1640
+ this._rewriteLastMsg(s.id, lastMsg.content);
1641
+ } else {
1642
+ const msg = { role: 'user', content: body };
1643
+ s.messages.push(msg);
1644
+ try {
1645
+ this._append(s, msg);
1646
+ } catch {}
1647
+ }
1648
+ this.emit({ type: 'observe', sessionId: s.id });
1649
+ this.emit({ type: 'sessions' });
1650
+ return true;
1651
+ }
1652
+
1614
1653
  /* #14 paralel ajanlar: ANA oturumu hiç bloklamayan arka plan çalıştırıcı.
1615
1654
  Ayrı gizli oturum açar, işi orada koşturur; bitince özet bildirim
1616
1655
  istenen sohbete düşer. Birden fazla iş aynı anda paralel koşabilir —
@@ -1812,7 +1851,7 @@ class Engine {
1812
1851
  if (j.status === 'running' || j.status === 'queued') {
1813
1852
  j.status = 'aborted';
1814
1853
  j.endedAt = new Date().toISOString();
1815
- j.error = String(j.error || '') || 'uygulama kapatıldı';
1854
+ j.error = String(j.error || '') || 'uygulama kapatıldı — paralel ajan yarıda kesildi';
1816
1855
  migrated = true;
1817
1856
  }
1818
1857
  j._persisted = true;
@@ -1905,9 +1944,18 @@ class Engine {
1905
1944
  this._pendingReports = this._pendingReports || [];
1906
1945
  this._pendingReports.push({ parentId: job.parentId, text });
1907
1946
  this.flushPendingReports(job.parentId);
1908
- } else if (status === 'aborted' && grouped) {
1909
- /* iptal edilen üye gruptan düşmüş sayılır */
1910
- this._bgGroupRecord(job.title, 'aborted', '');
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
+ }
1911
1959
  }
1912
1960
  }
1913
1961
 
@@ -2397,8 +2445,21 @@ class Engine {
2397
2445
  const aborted = e && (e.name === 'AbortError' || ctrl.signal.aborted);
2398
2446
  if (aborted) {
2399
2447
  this._clearCrash(); // kullanıcı durdurdu — kurtarma yok
2400
- emit({ type: 'done', aborted: true });
2401
- this._bgFinish(sid, 'aborted');
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
+ }
2402
2463
  } else {
2403
2464
  // FALLOUT: tüm zincir tükendi — durumu kaydet, açılışta kaldığı yerden devam edilir
2404
2465
  this._saveCrash(sid, e, 'chain-exhausted');
@@ -2641,7 +2702,7 @@ class Engine {
2641
2702
  if (!this.ctrls.has(pairId)) break;
2642
2703
  }
2643
2704
  if (this.ctrls.has(pairId)) {
2644
- try { this.interrupt(pairId); } catch {}
2705
+ try { this.interrupt(pairId, 'bot DM zaman aşımına uğradı (120 sn) — tur kesildi'); } catch {}
2645
2706
  return { ok: false, error: 'hedef bot zaman aşımına uğradı (120 sn)' };
2646
2707
  }
2647
2708
  const after = this.cache.get(pairId) || pair;
@@ -2755,39 +2816,19 @@ class Engine {
2755
2816
  }
2756
2817
  }
2757
2818
 
2758
- /* web_search zinciri — web_search VE deep_search aynı zinciri kullanır:
2759
- 1) dahili tarayıcı (DİREK GOOGLE ücretsiz + AI cevabı)
2760
- 2) TinyFish (tarayıcı sorun çıkarsa HEMEN devreye girer — anahtar girildiyse)
2761
- 3) python çoklu-motor (ddgs / DDG+Bing+Mojeek)
2762
- 4) Exa (anahtar girildiyse, son çare)
2763
- Tarayıcıda CAPTCHA/"olağandışı trafik"/boş sonuç algılanırsa 10 dk boyunca
2764
- tarayıcı ATLANIR — her arama TinyFish'ten başlar, süre dolunca tarayıcı geri döner. */
2765
- async _webSearchChain(q, n, sessionId, signal) {
2766
- let r = null;
2767
- 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) {
2768
2826
  const browserUsable = this.browser && typeof this.browser.search === 'function';
2769
- if (Date.now() < (this._webSearchBrowserBanUntil || 0)) banned = true;
2770
- if (browserUsable && !banned) {
2771
- try { r = await this.browser.search(q, signal, { sessionId }); } catch {}
2772
- if (!r || !r.ok || !(r.results || []).length) {
2773
- /* tarayıcı sorunlu — 10 dk atla, alternatif zincir hemen devrede */
2774
- this._webSearchBrowserBanUntil = Date.now() + 10 * 60 * 1000;
2775
- r = null;
2776
- banned = true;
2777
- }
2778
- }
2779
- if (!r || !r.ok || !(r.results || []).length) {
2780
- try { r = await tools.tinyfishSearch(q, n, signal); } catch {}
2781
- }
2782
- if (!r || !r.ok || !(r.results || []).length) {
2783
- try {
2784
- r = JSON.parse(await tools.exec('web_search', { query: q, max_results: n }, { cwd: this._sessionWorkspace(sessionId), signal }));
2785
- } catch {}
2786
- }
2787
- const out = r || { ok: false, error: 'web arama başarısız — tüm motorlar boş döndü' };
2788
- if (out && out.ok && banned) out.note = 'tarayıcı araması 10 dk askıda (CAPTCHA/trafik) — TinyFish/alternatif zincir kullanıldı';
2789
- return out;
2790
- }
2827
+ return tools.searchChainWeb(q, n, {
2828
+ signal,
2829
+ browser: browserUsable ? () => this.browser.search(q, signal, { sessionId }) : null,
2830
+ });
2831
+ }
2791
2832
 
2792
2833
  /* Dahili OCR aracı: görüntüden metin çıkarır (görsel desteklemeyen modeller için).
2793
2834
  Kaynak: 'browser' (dahili tarayıcı görüntüsü, varsayılan) | 'screen' (masaüstü) | dosya yolu. */
@@ -3016,8 +3057,11 @@ class Engine {
3016
3057
  }
3017
3058
  if (name === 'task_cancel') {
3018
3059
  const id = String(args.id || '');
3019
- const ok = this.interrupt(id);
3020
- return JSON.stringify(ok ? { ok: true, id } : { ok: false, error: 'çalışan ajan yok: ' + id });
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 });
3021
3065
  }
3022
3066
  if (name === 'set_reminder') {
3023
3067
  if (!this.reminders || typeof this.reminders.add !== 'function') {
@@ -3139,10 +3183,8 @@ class Engine {
3139
3183
  }
3140
3184
  if (name === 'deep_search') {
3141
3185
  /* agentic derin araştırma: çoklu sorgu + gizli tarayıcıda sayfa okuma.
3142
- ARAMA zinciri web_search ile BİREBİR AYNI — eski sürüm yalnız python
3143
- motorlarını (ddgs/DDG/Bing) kullanıyordu; CAPTCHA/engel sonucu motorlar
3144
- boş dönünce "tüm motorlar boş döndü" hatası veriyordu. Artık önce
3145
- 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). */
3146
3188
  try {
3147
3189
  emitSafe(this, sessionId, { type: 'status', status: 'derin araştırma: çoklu sorgu + gizli sayfa okuma' });
3148
3190
  } catch {}
@@ -3207,9 +3249,34 @@ class Engine {
3207
3249
  }
3208
3250
  }
3209
3251
 
3210
- interrupt(sessionId) {
3211
- const c = this.ctrls.get(String(sessionId));
3212
- if (!c) return false;
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);
3213
3280
  c.abort();
3214
3281
  return true;
3215
3282
  }
@@ -3219,19 +3286,22 @@ class Engine {
3219
3286
  main katmanında BAĞIMSIZ yaşar — /stop onlara dokunmaz. */
3220
3287
  stopAll() {
3221
3288
  let aborted = 0;
3289
+ const why = '/stop: kullanıcı tüm ajanları ve turları durdurdu';
3222
3290
  /* fan-out gruplarını kapat — yarım grup artık birleşik rapor beklemesin */
3223
3291
  if (this._bgGroups) {
3224
3292
  for (const g of this._bgGroups.values()) g.dead = true;
3225
3293
  }
3226
3294
  if (this._bgPendingStart) this._bgPendingStart.clear();
3295
+ this._abortReasons = this._abortReasons || new Map();
3227
3296
  for (const [sid, c] of this.ctrls) {
3228
- try { c.abort(); aborted++; } catch {}
3297
+ try { this._abortReasons.set(sid, why); c.abort(); aborted++; } catch {}
3229
3298
  /* abort sonrası _run finally bloğu temizler; paralel ajan kaydını biz işaretleyelim */
3230
3299
  if (this._bgJobs.has(sid)) {
3231
3300
  const job = this._bgJobs.get(sid);
3232
3301
  if (job.status === 'running' || job.status === 'queued') {
3233
3302
  job.status = 'aborted';
3234
3303
  job.endedAt = nowIso();
3304
+ job.error = job.error || why;
3235
3305
  }
3236
3306
  job.revive = false; // bekleyen öz-kurtarma gönderimini de öldür
3237
3307
  this._clearKicks(sid);
@@ -3242,6 +3312,7 @@ class Engine {
3242
3312
  if (j.status === 'queued') {
3243
3313
  j.status = 'aborted';
3244
3314
  j.endedAt = nowIso();
3315
+ j.error = j.error || why;
3245
3316
  aborted++;
3246
3317
  }
3247
3318
  }
@@ -3613,13 +3684,15 @@ const TOOLS = [
3613
3684
  type: 'function',
3614
3685
  function: {
3615
3686
  name: 'task_cancel',
3616
- description: 'Cancel a running background agent by id (from tasks_list).',
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.',
3617
3689
  parameters: {
3618
3690
  type: 'object',
3619
3691
  properties: {
3620
3692
  id: { type: 'string' },
3693
+ reason: { type: 'string', description: 'REQUIRED: why you are cancelling this agent (one short sentence)' },
3621
3694
  },
3622
- required: ['id'],
3695
+ required: ['id', 'reason'],
3623
3696
  },
3624
3697
  },
3625
3698
  },
@@ -3850,3 +3923,4 @@ module.exports.BG_HIDDEN_TOOLS = BG_HIDDEN_TOOLS;
3850
3923
  module.exports.PERM_TOOL_SETS = PERM_TOOL_SETS;
3851
3924
  module.exports.PERM_LEVELS = PERM_LEVELS;
3852
3925
  module.exports.normalizePerms = normalizePerms;
3926
+ module.exports.OBSERVE_MARK = OBSERVE_MARK;
@@ -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
+ };
@@ -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) 3) Exa (son çare, anahtar varsa). Takılmama kuralları içerir.
168
- version: 1.3.2
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. Python çoklu-motor (tarayıcı sonuç vermezse)
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
  },