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.
@@ -7,6 +7,7 @@ const https = require('https');
7
7
  const { execFile } = require('child_process');
8
8
  const { spawn } = require('child_process');
9
9
  const research = require('./research');
10
+ const obscura = require('./obscura');
10
11
 
11
12
  const MAX_CMD_OUTPUT = 16000;
12
13
  const MAX_FILE_CHARS = 200000;
@@ -627,7 +628,7 @@ const definitions = [
627
628
  function: {
628
629
  name: 'web_search',
629
630
  description:
630
- 'FAST web search with an automatic chain: built-in browser first (real Chromium searching GOOGLE directly with AI Mode — no bot protection; the response may include an `ai` field holding Google\'s own AI answer, ideal for "who/what is X" questions), then TinyFish API (if a key is configured, used IMMEDIATELY whenever the browser hits CAPTCHA/unusual traffic — the browser is then skipped for 10 minutes), then Python multi-engine (ddgs / DuckDuckGo+Bing+Mojeek parallel), then Exa API as last resort if a key is configured (Ayarlar Web Arama). Returns {ai?, results[{title,url,snippet}]}. Use when fresh or external info is needed; skip for things you already know.',
631
+ 'FAST web search with an automatic chain (order is configurable in Ayarlar → Web Arama): built-in browser (real Chromium searching GOOGLE directly with AI Mode — no bot protection; the response may include an `ai` field holding Google\'s own AI answer), Obscura stealth headless browser (anti-detect; searches DuckDuckGo, installed automatically and ACTIVE by default), TinyFish API (only if a key is configured), then Python multi-engine (ddgs / DuckDuckGo+Bing+Mojeek). If the browser hits CAPTCHA/unusual traffic it is skipped for 10 minutes and the next engine takes over. Returns {ai?, results[{title,url,snippet}]}. Use when fresh or external info is needed; skip for things you already know.',
631
632
  parameters: {
632
633
  type: 'object',
633
634
  properties: {
@@ -758,13 +759,9 @@ async function exec(name, args, ctx) {
758
759
  case 'web_search': {
759
760
  const q = String(args.query || '');
760
761
  const n = Number(args.max_results) || 8;
761
- /* önce ücretsiz python çoklu-motor; Exa SON ÇARE (dahili tarayıcı zincirin
762
- üstünde engine._execTool'da çalışır) */
763
- let r = await webSearchFast(q, { maxResults: n, signal: ctx.signal });
764
- if (!r || !r.ok || !(r.results || []).length) {
765
- const exa = await exaSearch(q, n, ctx.signal);
766
- if (exa) return JSON.stringify(exa);
767
- }
762
+ /* sıralı zincir (obscura/tinyfish/python dahili tarayıcı engine
763
+ tarafındaki hook'tan gelir); Exa KALDIRILDI */
764
+ const r = await searchChainWeb(q, n, { signal: ctx.signal });
768
765
  return JSON.stringify(r);
769
766
  }
770
767
  case 'deep_search': {
@@ -772,7 +769,7 @@ async function exec(name, args, ctx) {
772
769
  Gizli tarayıcı okuması engine.research hook'undan gelir (main process). */
773
770
  const r = await research.deepSearch(
774
771
  args,
775
- { search: (q) => webSearchFast(q, { maxResults: 10, signal: ctx.signal }) },
772
+ { search: (q) => searchChainWeb(q, 10, { signal: ctx.signal }) },
776
773
  ctx.signal
777
774
  );
778
775
  return JSON.stringify(r);
@@ -836,53 +833,101 @@ async function exec(name, args, ctx) {
836
833
  }
837
834
  }
838
835
 
839
- /* ---------- Exa (opsiyonel ücretli arama) ----------
840
- Ayarlar Web Arama'dan anahtar girilirse web_search ÖNCE Exa'yı dener;
841
- hata/sonuç yoksa ücretsiz python çoklu-motor devreye girer. */
842
- let _exaKey = null;
836
+ /* ---------- Sıralı arama zinciri (Ayarlar Web Arama'dan değiştirilir) ----------
837
+ Motorlar: browser (dahili Chromium Google) · obscura (stealth headless →
838
+ DuckDuckGo) · tinyfish (API, anahtar gerekir) · python (ddgs/DDG+Bing+Mojeek).
839
+ Sıra + aç/kapa ayarı settings.json'da (searchChain) saklanır; Obscura
840
+ varsayılan AKTİF. Tarayıcı CAPTCHA/trafik verirse 10 dk atlanır. */
841
+ const SEARCH_ENGINE_IDS = ['browser', 'obscura', 'tinyfish', 'python'];
842
+ const DEFAULT_SEARCH_CHAIN = SEARCH_ENGINE_IDS.map((id) => ({ id, on: true }));
843
+
844
+ let _searchChain = DEFAULT_SEARCH_CHAIN.map((x) => ({ ...x }));
845
+ let _obscuraEnabled = true; /* Obscura varsayılan AKTİF */
846
+ let _browserBanUntil = 0;
847
+
848
+ function normalizeSearchChain(list) {
849
+ const arr = Array.isArray(list) ? list : [];
850
+ const rows = [];
851
+ const seen = new Set();
852
+ for (const item of arr) {
853
+ const id = String((item && item.id) || item || '').trim();
854
+ if (!SEARCH_ENGINE_IDS.includes(id) || seen.has(id)) continue;
855
+ seen.add(id);
856
+ rows.push({ id, on: !(item && item.on === false) });
857
+ }
858
+ for (const id of SEARCH_ENGINE_IDS) {
859
+ if (!seen.has(id)) rows.push({ id, on: true }); /* listede eksik motor varsayılan AÇIK */
860
+ }
861
+ /* obscura varsayılan AKTİF; zincir boş kalmasın: hepsi kapalıysa browser'ı aç */
862
+ if (!rows.some((r) => r.on)) {
863
+ const b = rows.find((r) => r.id === 'browser');
864
+ if (b) b.on = true;
865
+ }
866
+ return rows;
867
+ }
843
868
 
844
- function setExaKey(key) {
845
- _exaKey = String(key || '').trim() || null;
869
+ function setSearchChain(list) {
870
+ _searchChain = normalizeSearchChain(list);
871
+ return _searchChain;
846
872
  }
847
873
 
848
- async function exaSearch(query, maxResults, signal) {
849
- if (!_exaKey) return null;
850
- try {
851
- const sig =
852
- signal && typeof AbortSignal !== 'undefined' && AbortSignal.any
853
- ? AbortSignal.any([signal, AbortSignal.timeout(20000)])
854
- : AbortSignal.timeout(20000);
855
- const r = await fetch('https://api.exa.ai/search', {
856
- method: 'POST',
857
- headers: { 'x-api-key': _exaKey, 'Content-Type': 'application/json' },
858
- body: JSON.stringify({
859
- query,
860
- numResults: Math.max(1, Math.min(12, Number(maxResults) || 8)),
861
- contents: { text: { maxCharacters: 1200 } },
862
- }),
863
- signal: sig,
864
- });
865
- if (!r.ok) return null;
866
- const j = await r.json();
867
- const results = (j.results || [])
868
- .map((x) => ({
869
- title: String(x.title || ''),
870
- url: String(x.url || ''),
871
- snippet: String(x.text || '').replace(/\s+/g, ' ').slice(0, 400),
872
- engine: 'exa',
873
- }))
874
- .filter((x) => x.url && x.title);
875
- if (!results.length) return null;
876
- return { ok: true, engine: 'exa', query, results };
877
- } catch {
878
- return null;
874
+ function setSearchObscuraEnabled(v) {
875
+ _obscuraEnabled = v !== false;
876
+ }
877
+
878
+ function getSearchChain() {
879
+ return _searchChain.map((x) => ({ ...x }));
880
+ }
881
+
882
+ function browserBanned() {
883
+ return Date.now() < _browserBanUntil;
884
+ }
885
+
886
+ function banBrowser(minutes = 10) {
887
+ _browserBanUntil = Date.now() + minutes * 60 * 1000;
888
+ }
889
+
890
+ /* Zinciri sırayla koştur: her motor boş/hata dönerse sıradakine geç.
891
+ browser motoru yalnız engine'den gelir (hook); araç-bağımsız çağrılarda atlanır. */
892
+ async function searchChainWeb(query, maxResults, { signal, browser } = {}) {
893
+ const q = String(query || '').trim().slice(0, 400);
894
+ if (!q) return { ok: false, error: 'boş sorgu' };
895
+ const cap = Math.max(1, Math.min(12, Number(maxResults) || 8));
896
+ let out = null;
897
+ const banned = browserBanned();
898
+ for (const row of _searchChain) {
899
+ if (out && out.ok && (out.results || []).length) break;
900
+ if (!row.on) continue;
901
+ try {
902
+ if (row.id === 'browser') {
903
+ if (typeof browser !== 'function' || banned) continue;
904
+ out = await browser();
905
+ if (!out || !out.ok || !(out.results || []).length) {
906
+ banBrowser(); /* tarayıcı sorunlu — 10 dk atla, alternatifler devrede */
907
+ out = null;
908
+ }
909
+ } else if (row.id === 'obscura') {
910
+ if (!_obscuraEnabled) continue;
911
+ out = await obscura.obscuraSearch(q, { maxResults: cap, signal });
912
+ } else if (row.id === 'tinyfish') {
913
+ out = await tinyfishSearch(q, cap, signal);
914
+ } else if (row.id === 'python') {
915
+ out = await webSearchFast(q, { maxResults: cap, signal });
916
+ }
917
+ } catch {
918
+ out = null;
919
+ }
920
+ if (out && out.ok && !(out.results || []).length) out = null; /* boş → sıradaki motor */
921
+ }
922
+ if (out && out.ok && banned && (out.results || []).length) {
923
+ out.note = 'dahili tarayıcı 10 dk askıda (CAPTCHA/trafik) — alternatif motor kullanıldı';
879
924
  }
925
+ return out || { ok: false, error: 'web arama başarısız — tüm motorlar boş döndü' };
880
926
  }
881
927
 
882
- /* ---------- TinyFish (ücretsiz arama; anahtar girilirse ZİNCİRİN BAŞI) ----------
928
+ /* ---------- TinyFish (anahtar girilirse zincirdeki kendi sırasında) ----------
883
929
  GET https://api.search.tinyfish.ai?query=... · Header: X-API-Key
884
- Ayarlar Web Arama'dan anahtar girilirse web_search ÖNCE TinyFish'i dener;
885
- hata/sonuç yoksa eski zincir (tarayıcı → python çoklu-motor → Exa) devam eder. */
930
+ Anahtar yoksa bu motor otomatik atlanır; sıradaki motor devreye girer. */
886
931
  let _tinyfishKey = null;
887
932
 
888
933
  function setTinyfishKey(key) {
@@ -936,10 +981,19 @@ module.exports = {
936
981
  webSearchFast,
937
982
  seedScript,
938
983
  bundledScriptPath,
939
- setExaKey,
940
- exaSearch,
984
+ /* arama zinciri (sıra + aç/kapa Ayarlar → Web Arama'dan) */
985
+ SEARCH_ENGINE_IDS,
986
+ DEFAULT_SEARCH_CHAIN,
987
+ setSearchChain,
988
+ setSearchObscuraEnabled,
989
+ getSearchChain,
990
+ searchChainWeb,
991
+ browserBanned,
992
+ banBrowser,
941
993
  setTinyfishKey,
942
994
  tinyfishSearch,
995
+ /* obscura geçişi */
996
+ obscura,
943
997
  /* python altyapısı */
944
998
  ensurePython,
945
999
  findSystemPython,
package/src/main.js CHANGED
@@ -150,7 +150,9 @@ function startNpmUpdateWatch() {
150
150
  check();
151
151
  setInterval(check, 6 * 60 * 60 * 1000);
152
152
  }
153
- const { htmlToText, setExaKey, setTinyfishKey } = require('./agent/tools');
153
+ const toolsMod = require('./agent/tools');
154
+ const { htmlToText, setSearchChain, setSearchObscuraEnabled, setTinyfishKey } = toolsMod;
155
+ const obscura = require('./agent/obscura');
154
156
  const { waToolLine } = require('./agent/watext');
155
157
 
156
158
  /* #3 merkezî log sistemine process-seviye hataları da düşsün */
@@ -219,8 +221,17 @@ let engine = null;
219
221
  let settings = loadSettings();
220
222
  ensureBeastCode();
221
223
  startHealthServer(); /* splash/boot aşamasından itibaren /health ayakta */
222
- try { setExaKey(settings.exaKey || null); } catch {}
224
+ try { setSearchObscuraEnabled(settings.obscuraEnabled !== false); } catch {} /* Obscura varsayılan AKTİF */
225
+ try { setSearchChain(settings.searchChain); } catch {}
223
226
  try { setTinyfishKey(settings.tinyfishKey || null); } catch {}
227
+ /* kurulumda Obscura da kurulsun: yoksa ARKA PLANDA otomatik indir (UI kilitlenmez) */
228
+ setTimeout(() => {
229
+ if (!obscura.obscuraInstalled()) {
230
+ obscura.installObscura().then((r) => {
231
+ console.log('[obscura]', r.ok ? 'kuruldu: ' + r.dir : 'kurulamadı: ' + (r.error || '?'));
232
+ }).catch((e) => console.log('[obscura] kurulamadı:', String((e && e.message) || e)));
233
+ }
234
+ }, 4000).unref?.();
224
235
  let wa = null;
225
236
  let waChats = new Map(); // jid -> aktif session id
226
237
  let waHistory = new Map(); // jid -> [sid,...] bu sohbete ait tüm oturumlar
@@ -4118,7 +4129,7 @@ ipcMain.handle('agent:interrupt', (_e, sessionId) => {
4118
4129
  desktopQueue.delete(String(sessionId));
4119
4130
  }
4120
4131
  } catch {}
4121
- return engine.interrupt(sessionId);
4132
+ return engine.interrupt(sessionId, 'kullanıcı sohbetteki durdurma (■) düğmesiyle iptal etti');
4122
4133
  });
4123
4134
 
4124
4135
  /* ---------- masaüstü sohbet birleştirme ----------
@@ -4482,7 +4493,9 @@ ipcMain.handle('memory:get', () => memory.loadAll());
4482
4493
  /* paralel ajanlar: canlı izleme (#14) */
4483
4494
  ipcMain.handle('agents:list', () => engine.listBgJobs());
4484
4495
  ipcMain.handle('agents:detail', (_e, id) => engine.bgDetail(id));
4485
- ipcMain.handle('agents:cancel', (_e, id) => ({ ok: engine.interrupt(String(id || '')) }));
4496
+ ipcMain.handle('agents:cancel', (_e, id) => ({
4497
+ ok: engine.interrupt(String(id || ''), 'kullanıcı Paralel Ajanlar panelinden (×) iptal etti'),
4498
+ }));
4486
4499
  ipcMain.handle('ceo:get', () => !!engine.ceoMode);
4487
4500
  ipcMain.handle('ceo:set', (_e, v) => {
4488
4501
  settings.ceoMode = !!v;
@@ -5190,7 +5203,7 @@ ipcMain.handle('beastcode:stop', () => {
5190
5203
  const sid = bcSessions.get(ideRoot());
5191
5204
  if (!sid) return { ok: false };
5192
5205
  let r = false;
5193
- try { r = engine.interrupt(sid); } catch {}
5206
+ try { r = engine.interrupt(sid, 'kullanıcı Beast Code panelinden ■ ile durdurdu'); } catch {}
5194
5207
  return { ok: !!r };
5195
5208
  });
5196
5209
 
@@ -5211,26 +5224,38 @@ ipcMain.handle('think:set', (_e, v) => {
5211
5224
  return engine.publicState();
5212
5225
  });
5213
5226
 
5214
- /* Exa web arama anahtarı (Ayarlar → Web Arama) */
5215
- ipcMain.handle('exa:get', () => {
5216
- const k = settings.exaKey || '';
5217
- return { set: !!k, masked: k ? '••••••••' + k.slice(-4) : '' };
5218
- });
5219
- ipcMain.handle('exa:set', (_e, key) => {
5220
- const v = String(key || '').trim();
5221
- if (v) {
5222
- settings.exaKey = v;
5223
- saveSettings();
5224
- setExaKey(settings.exaKey);
5227
+ /* Obscura stealth headless tarayıcı (Ayarlar → Web Arama) */
5228
+ ipcMain.handle('obscura:get', () => ({
5229
+ installed: obscura.obscuraInstalled(),
5230
+ dir: obscura.obscuraDir(),
5231
+ enabled: settings.obscuraEnabled !== false,
5232
+ }));
5233
+ ipcMain.handle('obscura:install', async () => {
5234
+ try {
5235
+ const r = await obscura.installObscura();
5236
+ return { ok: !!r.ok, dir: r.dir || obscura.obscuraDir(), error: r.error || null };
5237
+ } catch (e) {
5238
+ return { ok: false, error: String((e && e.message) || e) };
5225
5239
  }
5226
- const k = settings.exaKey || '';
5227
- return { ok: true, set: !!k, masked: k ? '••••••••' + k.slice(-4) : '' };
5228
5240
  });
5229
- ipcMain.handle('exa:clear', () => {
5230
- settings.exaKey = '';
5241
+ ipcMain.handle('obscura:setEnabled', (_e, v) => {
5242
+ settings.obscuraEnabled = v !== false;
5231
5243
  saveSettings();
5232
- setExaKey(null);
5233
- return { ok: true, set: false, masked: '' };
5244
+ try { setSearchObscuraEnabled(settings.obscuraEnabled); } catch {}
5245
+ return { ok: true, enabled: settings.obscuraEnabled };
5246
+ });
5247
+
5248
+ /* Arama zinciri sırası (Ayarlar → Web Arama'dan değiştirilir) */
5249
+ ipcMain.handle('searchorder:get', () => ({ chain: toolsMod.getSearchChain() }));
5250
+ ipcMain.handle('searchorder:set', (_e, chain) => {
5251
+ try {
5252
+ const rows = setSearchChain(chain);
5253
+ settings.searchChain = rows;
5254
+ saveSettings();
5255
+ return { ok: true, chain: rows };
5256
+ } catch (e) {
5257
+ return { ok: false, error: String((e && e.message) || e) };
5258
+ }
5234
5259
  });
5235
5260
 
5236
5261
  /* #TinyFish: anahtar girilirse web_search zincirinin BAŞINDA kullanılır */
package/src/preload.js CHANGED
@@ -70,9 +70,11 @@ contextBridge.exposeInMainWorld('beast', {
70
70
  beastcodeStop: () => ipcRenderer.invoke('beastcode:stop'),
71
71
  beastcodeNew: () => ipcRenderer.invoke('beastcode:new'),
72
72
  thinkSet: (v) => ipcRenderer.invoke('think:set', v),
73
- exaGet: () => ipcRenderer.invoke('exa:get'),
74
- exaSet: (key) => ipcRenderer.invoke('exa:set', key),
75
- exaClear: () => ipcRenderer.invoke('exa:clear'),
73
+ obscuraGet: () => ipcRenderer.invoke('obscura:get'),
74
+ obscuraInstall: () => ipcRenderer.invoke('obscura:install'),
75
+ obscuraSetEnabled: (v) => ipcRenderer.invoke('obscura:setEnabled', v),
76
+ searchOrderGet: () => ipcRenderer.invoke('searchorder:get'),
77
+ searchOrderSet: (chain) => ipcRenderer.invoke('searchorder:set', chain),
76
78
  tinyfishGet: () => ipcRenderer.invoke('tinyfish:get'),
77
79
  tinyfishSet: (key) => ipcRenderer.invoke('tinyfish:set', key),
78
80
  tinyfishClear: () => ipcRenderer.invoke('tinyfish:clear'),
@@ -111,10 +111,10 @@
111
111
  lim_saved_toast: 'Limitler kaydedildi',
112
112
  lim_unlimited: 'limitsiz',
113
113
  tf_h2: 'TinyFish Arama',
114
- tf_sub: 'Ücretsiz, hızlı web arama API\u2019si. Anahtar girilirse web_search zincirinin EN BAŞINDA kullanılır; sonuç yoksa eski zincir (tarayıcı → çoklu-motor Exa) devam eder.',
114
+ tf_sub: 'Ücretsiz, hızlı web arama API\u2019si. Anahtar girilirse zincirde KENDİ SIRASINDA kullanılır; anahtar yoksa motor otomatik atlanır.',
115
115
  tf_key_label: 'TinyFish API anahtarı (agent.tinyfish.ai/api-keys)',
116
116
  tf_status_set: 'TinyFish aktif — kayıtlı anahtar: ',
117
- tf_status_unset: 'TinyFish pasif — eski arama zinciri kullanılıyor.',
117
+ tf_status_unset: 'TinyFish pasif — zincir sıradaki motorla devam eder.',
118
118
  tf_empty_toast: 'Anahtar boş — mevcut anahtar korundu',
119
119
  tf_saved_toast: 'TinyFish anahtarı kaydedildi',
120
120
  tf_cleared_toast: 'TinyFish anahtarı silindi',
@@ -260,18 +260,30 @@
260
260
  ws_calls: 'çağrı',
261
261
  ws_token: 'token',
262
262
  ws_h2: 'Web Arama',
263
- ws_sub: 'Arama zinciri: 1) dahili tarayıcı (direk Google) 2) python çoklu-motor (ddgs / DDG+Bing+Mojeek) 3) Exa — otomatik. Exa anahtarı yalnızca son çare olarak kullanılır.',
264
- ws_exa_label: 'Exa API anahtarı (exa.ai)',
265
- ws_exa_ph: 'masaüstü görünümde maskeli',
263
+ ws_sub: 'Arama zinciri: 1) dahili tarayıcı (direk Google) 2) Obscura (stealth headless → DuckDuckGo) 3) TinyFish (anahtar varsa) 4) python çoklu-motor (ddgs / DDG+Bing+Mojeek) — otomatik. Sıra ve aç/kapa aşağıdan değiştirilir.',
264
+ oc_h2: 'Obscura (Stealth Tarayıcı)',
265
+ oc_sub: 'Rust tabanlı gizli headless tarayıcı (anti-detect + V8, Chromium\u2019suz). Kurulu değilse ilk açılışta otomatik indirilir (%APPDATA%\\beast\\obscura); DuckDuckGo aramasını bot korumasını aşarak yapar.',
266
+ oc_status_ok: 'Obscura kurulu ve hazır.',
267
+ oc_status_missing: 'Obscura kurulu değil — ilk açılışta otomatik indirilir (≈74 MB).',
268
+ oc_install: 'Kur / Güncelle',
269
+ oc_installing: 'İndiriliyor… (≈74 MB, bağlantıya göre birkaç dakika)',
270
+ oc_installed_toast: 'Obscura kuruldu',
271
+ oc_install_fail: 'Kurulamadı: ',
272
+ oc_enabled: 'Obscura arama zincirinde aktif',
273
+ oc_on_toast: 'Obscura aktif',
274
+ oc_off_toast: 'Obscura pasif',
275
+ so_h2: 'Arama Sırası',
276
+ so_sub: 'Motorlar bu sırayla denenir; boş dönen motor atlanıp sıradaki devreye girer. ↑↓ ile sırayı, kutucukla aç/kapa durumunu değiştir.',
277
+ so_engine_browser: 'Dahili Tarayıcı (Google)',
278
+ so_engine_obscura: 'Obscura (Stealth → DuckDuckGo)',
279
+ so_engine_tinyfish: 'TinyFish API (anahtar gerekir)',
280
+ so_engine_python: 'Python Çoklu-Motor (ddgs/DDG/Bing/Mojeek)',
281
+ so_saved_toast: 'Arama sırası kaydedildi',
266
282
  ws_save: 'Kaydet',
267
283
  ws_clear: 'Anahtarı Sil',
268
284
  ws_note: 'Anahtar maskeli tutulur; yenisini yazıp kaydettiğinde eskisinin yerine geçer. Boş kaydet: mevcut anahtar korunur.',
269
- ws_status_set: 'Exa aktif — kayıtlı anahtar: ',
270
- ws_status_unset: 'Exa yapılandırılmadı — ücretsiz python çoklu-motor arama kullanılıyor.',
271
285
  ws_empty_toast: 'Anahtar boş — mevcut anahtar korundu',
272
- ws_saved_toast: 'Exa anahtarı kaydedildi',
273
286
  ws_fail_toast: 'Kaydedilemedi',
274
- ws_cleared_toast: 'Exa anahtarı silindi',
275
287
  ag_h2: 'Paralel Ajanlar',
276
288
  ag_sub: 'CEO\u2019nun devrettiği arka plan işleri — canlı izleme',
277
289
  ag_ceo_label: 'CEO modu — konuşan ajan iş yapmaz, sadece emir verir',
@@ -300,12 +312,11 @@
300
312
  us_where_on_toast: 'Açılış özeti açık',
301
313
  us_where_off_toast: 'Açılış özeti kapandı',
302
314
  ws_h2: 'Web Arama',
303
- ws_exa_label: 'Exa API anahtarı (exa.ai)',
315
+ oc_status_ok: 'Obscura kurulu ve hazır.',
316
+ oc_status_missing: 'Obscura kurulu değil — ilk açılışta otomatik indirilir.',
304
317
  ws_save: 'Kaydet',
305
318
  ws_clear: 'Anahtarı Sil',
306
319
  ws_key_note: 'Anahtar maskeli tutulur; yenisini yazıp kaydettiğinde eskisinin yerine geçer. Boş kaydet: mevcut anahtar korunur.',
307
- ws_active: 'Exa aktif — kayıtlı anahtar: ',
308
- ws_notcfg: 'Exa yapılandırılmadı — ücretsiz python çoklu-motor arama kullanılıyor.',
309
320
  ws_empty: 'Anahtar boş — mevcut anahtar korundu',
310
321
  // terminal & tarayıcı & mini
311
322
  term_clear: 'Temizle',
@@ -641,10 +652,10 @@
641
652
  lim_saved_toast: 'Limits saved',
642
653
  lim_unlimited: 'unlimited',
643
654
  tf_h2: 'TinyFish Search',
644
- tf_sub: 'Free, fast web search API. If a key is set it is used at the TOP of the web_search chain; otherwise the old chain (browser → multi-engine Exa) continues.',
655
+ tf_sub: 'Free, fast web search API. If a key is set it is used in its own slot of the chain; without a key the engine is skipped automatically.',
645
656
  tf_key_label: 'TinyFish API key (agent.tinyfish.ai/api-keys)',
646
657
  tf_status_set: 'TinyFish active — saved key: ',
647
- tf_status_unset: 'TinyFish off — using the old search chain.',
658
+ tf_status_unset: 'TinyFish off — the chain continues with the next engine.',
648
659
  tf_empty_toast: 'Key empty — current key kept',
649
660
  tf_saved_toast: 'TinyFish key saved',
650
661
  tf_cleared_toast: 'TinyFish key deleted',
@@ -790,18 +801,30 @@
790
801
  ws_calls: 'calls',
791
802
  ws_token: 'tokens',
792
803
  ws_h2: 'Web Search',
793
- ws_sub: 'Search chain: 1) built-in browser (direct Google) 2) python multi-engine (ddgs / DDG+Bing+Mojeek) 3) Exa — automatic. The Exa key is only used as a last resort.',
794
- ws_exa_label: 'Exa API key (exa.ai)',
795
- ws_exa_ph: 'masked on desktop view',
804
+ ws_sub: 'Search chain: 1) built-in browser (direct Google) 2) Obscura (stealth headless → DuckDuckGo) 3) TinyFish (if a key is set) 4) python multi-engine (ddgs / DDG+Bing+Mojeek) — automatic. Order and on/off are editable below.',
805
+ oc_h2: 'Obscura (Stealth Browser)',
806
+ oc_sub: 'Rust-based stealth headless browser (anti-detect + V8, no Chromium). If not installed it is auto-downloaded at first launch (%APPDATA%\\beast\\obscura); it searches DuckDuckGo past bot protection.',
807
+ oc_status_ok: 'Obscura is installed and ready.',
808
+ oc_status_missing: 'Obscura is not installed — it is auto-downloaded at first launch (≈74 MB).',
809
+ oc_install: 'Install / Update',
810
+ oc_installing: 'Downloading… (≈74 MB, may take a few minutes)',
811
+ oc_installed_toast: 'Obscura installed',
812
+ oc_install_fail: 'Install failed: ',
813
+ oc_enabled: 'Obscura active in the search chain',
814
+ oc_on_toast: 'Obscura enabled',
815
+ oc_off_toast: 'Obscura disabled',
816
+ so_h2: 'Search Order',
817
+ so_sub: 'Engines are tried in this order; an empty engine is skipped and the next one takes over. Use ↑↓ to reorder, checkbox to enable/disable.',
818
+ so_engine_browser: 'Built-in Browser (Google)',
819
+ so_engine_obscura: 'Obscura (Stealth → DuckDuckGo)',
820
+ so_engine_tinyfish: 'TinyFish API (needs a key)',
821
+ so_engine_python: 'Python Multi-Engine (ddgs/DDG/Bing/Mojeek)',
822
+ so_saved_toast: 'Search order saved',
796
823
  ws_save: 'Save',
797
824
  ws_clear: 'Delete Key',
798
825
  ws_note: 'The key is kept masked; saving a new one replaces the old. Saving empty keeps the current key.',
799
- ws_status_set: 'Exa active — saved key: ',
800
- ws_status_unset: 'Exa not configured — using free python multi-engine search.',
801
826
  ws_empty_toast: 'Key empty — current key kept',
802
- ws_saved_toast: 'Exa key saved',
803
827
  ws_fail_toast: 'Could not save',
804
- ws_cleared_toast: 'Exa key deleted',
805
828
  ag_h2: 'Parallel Agents',
806
829
  ag_sub: 'Background tasks delegated by the CEO — live monitoring',
807
830
  ag_ceo_label: 'CEO mode — the talking agent does no work, only gives orders',
@@ -892,12 +915,11 @@
892
915
  us_where_on_toast: 'Startup summary on',
893
916
  us_where_off_toast: 'Startup summary off',
894
917
  ws_h2: 'Web Search',
895
- ws_exa_label: 'Exa API key (exa.ai)',
918
+ oc_status_ok: 'Obscura is installed and ready.',
919
+ oc_status_missing: 'Obscura is not installed — it is auto-downloaded at first launch.',
896
920
  ws_save: 'Save',
897
921
  ws_clear: 'Delete Key',
898
922
  ws_key_note: 'Key is kept masked; saving a new one replaces the old. Save empty: existing key kept.',
899
- ws_active: 'Exa active — saved key: ',
900
- ws_notcfg: 'Exa not configured — free python multi-engine search is used.',
901
923
  ws_empty: 'Key empty — existing key kept',
902
924
  term_clear: 'Clear',
903
925
  term_ph: 'type a command, press Enter…',