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.
@@ -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
@@ -7,7 +7,7 @@ const http = require('http');
7
7
  const dns = require('dns');
8
8
  const crypto = require('crypto');
9
9
  const { spawn } = require('child_process');
10
- const Engine = require('./agent/engine');
10
+ const { Engine, OBSERVE_MARK } = require('./agent/engine');
11
11
  const { loadBeastConfig, beastDir } = require('./agent/config');
12
12
  const bots = require('./agent/bots');
13
13
  const mqueue = require('./agent/mqueue');
@@ -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
@@ -1199,6 +1210,75 @@ async function waFlush(jid) {
1199
1210
  }
1200
1211
  }
1201
1212
 
1213
+ /* Grup göndereninin kimlik etiketi + SAHİP olup olmadığı.
1214
+ Sıra: izin listesindeki isim → @kullanıcı adı → gerçek PN → LID base.
1215
+ hitP.owner → bu kişi izin listesinde SAHİP olarak işaretli. */
1216
+ function waGroupSenderInfo(payload) {
1217
+ const pnDigits = String(payload.participantPn || '').split('@')[0].split(':')[0];
1218
+ const hitP = /^\d+$/.test(pnDigits) ? waFind(pnDigits) : null;
1219
+ const uname = String(payload.participantUsername || '').trim();
1220
+ let label;
1221
+ if (hitP && hitP.name) label = `${hitP.name} (+${pnDigits})`;
1222
+ else if (uname) label = `@${uname}${/^\d+$/.test(pnDigits) ? ' (+' + pnDigits + ')' : ''}`;
1223
+ else if (/^\d+$/.test(pnDigits)) label = '+' + pnDigits;
1224
+ else label = payload.participant ? '+' + String(payload.participant).split('@')[0].split(':')[0] : '';
1225
+ return { label, name: (hitP && hitP.name) || '', isOwner: !!(hitP && hitP.owner) };
1226
+ }
1227
+
1228
+ /* Oturum yoksa oluştur (processWaMessage ile aynı kalıp) */
1229
+ function ensureWaSession(jid) {
1230
+ let sid = waChats.get(jid);
1231
+ if (!sid) {
1232
+ const v = engine.createSession();
1233
+ sid = v.id;
1234
+ waChats.set(jid, sid);
1235
+ saveWaChats();
1236
+ if (wa) wa.setWatchJids([...waChats.keys()]);
1237
+ } else {
1238
+ waRememberSession(jid, sid);
1239
+ }
1240
+ return sid;
1241
+ }
1242
+
1243
+ /* ---------- GRUP BAĞLAM AKIŞI (mentionOnly + seeAll) ----------
1244
+ Bot grubun tüm konuşmasını okur ama CEVAP ÜRETMEZ; mesajlar sessizce
1245
+ oturum geçmişine bağlam olarak düşer. @mention gelince bot tüm bu
1246
+ bağlamı görerek konuşur. Anti-spam: aynı birleştirme penceresi. */
1247
+ const waCtxQueue = new Map(); // jid -> { timer, payloads[] }
1248
+
1249
+ function waGroupObserve(jid, payload, senderNum) {
1250
+ let q = waCtxQueue.get(jid);
1251
+ if (!q) {
1252
+ q = { payloads: [] };
1253
+ waCtxQueue.set(jid, q);
1254
+ }
1255
+ q.payloads.push({ payload, senderNum });
1256
+ clearTimeout(q.timer);
1257
+ waLog(`grup bağlam: kuyruğa girdi jid=${waPrettyJid(jid)} toplam=${q.payloads.length}`);
1258
+ q.timer = setTimeout(() => {
1259
+ waCtxFlush(jid).catch((e) => waLog(`ctx flush KRASİ: ${String((e && e.stack) || e)}`));
1260
+ }, WA_DEBOUNCE_MS);
1261
+ }
1262
+
1263
+ async function waCtxFlush(jid) {
1264
+ const q = waCtxQueue.get(jid);
1265
+ if (!q) return;
1266
+ waCtxQueue.delete(jid);
1267
+ const sid = ensureWaSession(jid);
1268
+ /* her satır gönderen etiketli olur — ajan kimin ne yazdığını izleyebilsin */
1269
+ const lines = [];
1270
+ for (const { payload } of q.payloads) {
1271
+ if (!payload || !payload.text) continue;
1272
+ const gi = waGroupSenderInfo(payload);
1273
+ const who = gi.label || '?';
1274
+ lines.push(`${who}: ${String(payload.text).slice(0, 1500)}`);
1275
+ }
1276
+ if (!lines.length) return;
1277
+ const text = `${OBSERVE_MARK} — WhatsApp grup konuşması (cevap verme, sadece bilgi olarak sakla)]\n` + lines.join('\n').slice(0, 6000);
1278
+ engine.observe(sid, text);
1279
+ waLog(`grup bağlam: oturuma işlendi sid=${sid} satır=${lines.length}`);
1280
+ }
1281
+
1202
1282
  /* ---------- WA'dan skill kurulumu ----------
1203
1283
  SKILL.md (veya *.skill.md) belgesi atılırsa skills altına kurulur. */
1204
1284
 
@@ -1549,7 +1629,15 @@ async function handleWaIncoming(jid, payload, senderNum) {
1549
1629
  if (isGroup) {
1550
1630
  const g = settings.waGroups || {};
1551
1631
  if (!g.enabled) return;
1552
- if (g.mentionOnly !== false && !payload.mentioned) return;
1632
+ const mentionMode = g.mentionOnly !== false;
1633
+ /* MENTION MODU + seeAll: bot grubun TÜM konuşmasını BAĞLAM olarak görür
1634
+ ama yalnız @mention'da cevap üretir. seeAll VARSAYILAN KAPALI —
1635
+ mention'sız mesajlar tamamen yutulur (gizlilik). */
1636
+ if (mentionMode && !payload.mentioned && g.seeAll) {
1637
+ waGroupObserve(jid, payload, senderNum);
1638
+ return;
1639
+ }
1640
+ if (mentionMode && !payload.mentioned) return;
1553
1641
  waLog(`grup mesajı jid=${waPrettyJid(jid)} participant=+${senderNum || '?'} mention=${!!payload.mentioned}`);
1554
1642
  } else {
1555
1643
  const hit = waFind(senderNum);
@@ -1725,22 +1813,21 @@ async function processWaMessage(jid, payload, senderNum, requeues = 0) {
1725
1813
  Kimlik çözümleme sırası: izin listesindeki isim → @kullanıcı adı → gerçek PN → LID.
1726
1814
  Ajan böylece grubun içinde mesajın KİMDEN geldiğini görür. */
1727
1815
  let groupSender = '';
1816
+ let groupSenderOwner = false;
1728
1817
  if (isGroup) {
1729
- const pnDigits = String(payload.participantPn || '').split('@')[0].split(':')[0];
1730
- const hitP = /^\d+$/.test(pnDigits) ? waFind(pnDigits) : null;
1731
- const uname = String(payload.participantUsername || '').trim();
1732
- if (hitP && hitP.name) groupSender = `${hitP.name} (+${pnDigits})`;
1733
- else if (uname) groupSender = `@${uname}${/^\d+$/.test(pnDigits) ? ' (+' + pnDigits + ')' : ''}`;
1734
- else if (/^\d+$/.test(pnDigits)) groupSender = '+' + pnDigits;
1735
- else groupSender = participantName || '';
1818
+ const gi = waGroupSenderInfo(payload);
1819
+ groupSender = gi.label || participantName;
1820
+ groupSenderOwner = gi.isOwner;
1736
1821
  }
1737
- /* #v13.1 rol: SAHİP vs MİSAFİR — ajan kime konuştuğunu net bilsin */
1822
+ /* #v13.1 rol: SAHİP vs MİSAFİR — ajan kime konuştuğunu net bilsin.
1823
+ GRUPLARDA DA: gönderen izin listesinde SAHİP olarak işaretliyse ajan
1824
+ bunu görür — sahibinin grup içi talepleri misafir sözünden önceliklidir. */
1738
1825
  const isOwner = !isGroup && !!hit.owner;
1739
- const roleTag = isOwner
1826
+ const roleTag = isOwner || groupSenderOwner
1740
1827
  ? 'SAHİBİN (talepleri önceliklidir)'
1741
1828
  : 'MİSAFİR (izinli ama sahibin sözü önceliklidir)';
1742
1829
  const label = isGroup
1743
- ? `Grup ${jid.split('@')[0]}${groupSender ? ' — gönderen: ' + groupSender : ''}`
1830
+ ? `Grup ${jid.split('@')[0]}${groupSender ? ` — gönderen: ${groupSender} — ${groupSenderOwner ? 'SAHİBİN' : 'MİSAFİR (grup üyesi)'}` : ''}`
1744
1831
  : hit.name
1745
1832
  ? `${hit.name} (+${senderNum || '?'}) — ${roleTag}`
1746
1833
  : `+${senderNum || '?'} — ${roleTag}`;
@@ -1748,7 +1835,9 @@ async function processWaMessage(jid, payload, senderNum, requeues = 0) {
1748
1835
  if (!isGroup && !isOwner) {
1749
1836
  text += `\n[NOT: Bu kişi SAHİP DEĞİL, misafirdir. Sahibin ayarlarını/verilerini değiştirme; kalıcı hafızaya misafire özel bilgi yazma.]`;
1750
1837
  } else if (isGroup) {
1751
- text += `\n[NOT: Grup mesajı — gönderen grubun üyesidir, izin listendeki kişi olmayabilir. Grup üyelerine karşı temkinli konuş.]`;
1838
+ text += groupSenderOwner
1839
+ ? `\n[NOT: Grup mesajı ama gönderen SAHİBİN — talepleri önceliklidir, misafir gibi temkinli konuşmana gerek yok.]`
1840
+ : `\n[NOT: Grup mesajı — gönderen grubun üyesidir, izin listendeki kişi olmayabilir. Grup üyelerine karşı temkinli konuş.]`;
1752
1841
  }
1753
1842
  const attachments = [];
1754
1843
 
@@ -4040,7 +4129,7 @@ ipcMain.handle('agent:interrupt', (_e, sessionId) => {
4040
4129
  desktopQueue.delete(String(sessionId));
4041
4130
  }
4042
4131
  } catch {}
4043
- return engine.interrupt(sessionId);
4132
+ return engine.interrupt(sessionId, 'kullanıcı sohbetteki durdurma (■) düğmesiyle iptal etti');
4044
4133
  });
4045
4134
 
4046
4135
  /* ---------- masaüstü sohbet birleştirme ----------
@@ -4404,7 +4493,9 @@ ipcMain.handle('memory:get', () => memory.loadAll());
4404
4493
  /* paralel ajanlar: canlı izleme (#14) */
4405
4494
  ipcMain.handle('agents:list', () => engine.listBgJobs());
4406
4495
  ipcMain.handle('agents:detail', (_e, id) => engine.bgDetail(id));
4407
- 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
+ }));
4408
4499
  ipcMain.handle('ceo:get', () => !!engine.ceoMode);
4409
4500
  ipcMain.handle('ceo:set', (_e, v) => {
4410
4501
  settings.ceoMode = !!v;
@@ -4551,12 +4642,16 @@ ipcMain.handle('wherewasi:set', (_e, cfg) => {
4551
4642
  return settings.whereWasI;
4552
4643
  });
4553
4644
 
4554
- /* WhatsApp grup ayarı: { enabled, mentionOnly } */
4555
- ipcMain.handle('wa:groups:get', () => settings.waGroups || { enabled: false, mentionOnly: true });
4645
+ /* WhatsApp grup ayarı: { enabled, mentionOnly, seeAll }
4646
+ seeAll: yalnız mentionOnly modunda anlamlı bot grubun TÜM konuşmasını
4647
+ bağlam olarak okur ama yine SADECE @mention'da cevap verir.
4648
+ VARSAYILAN KAPALI: herkesin konuşmasını görmesi istenmeyebilir. */
4649
+ ipcMain.handle('wa:groups:get', () => settings.waGroups || { enabled: false, mentionOnly: true, seeAll: false });
4556
4650
  ipcMain.handle('wa:groups:set', (_e, cfg) => {
4557
4651
  settings.waGroups = {
4558
4652
  enabled: !!(cfg && cfg.enabled),
4559
4653
  mentionOnly: !(cfg && cfg.mentionOnly === false),
4654
+ seeAll: !!(cfg && cfg.seeAll),
4560
4655
  };
4561
4656
  saveSettings();
4562
4657
  return settings.waGroups;
@@ -5108,7 +5203,7 @@ ipcMain.handle('beastcode:stop', () => {
5108
5203
  const sid = bcSessions.get(ideRoot());
5109
5204
  if (!sid) return { ok: false };
5110
5205
  let r = false;
5111
- try { r = engine.interrupt(sid); } catch {}
5206
+ try { r = engine.interrupt(sid, 'kullanıcı Beast Code panelinden ■ ile durdurdu'); } catch {}
5112
5207
  return { ok: !!r };
5113
5208
  });
5114
5209
 
@@ -5129,26 +5224,38 @@ ipcMain.handle('think:set', (_e, v) => {
5129
5224
  return engine.publicState();
5130
5225
  });
5131
5226
 
5132
- /* Exa web arama anahtarı (Ayarlar → Web Arama) */
5133
- ipcMain.handle('exa:get', () => {
5134
- const k = settings.exaKey || '';
5135
- return { set: !!k, masked: k ? '••••••••' + k.slice(-4) : '' };
5136
- });
5137
- ipcMain.handle('exa:set', (_e, key) => {
5138
- const v = String(key || '').trim();
5139
- if (v) {
5140
- settings.exaKey = v;
5141
- saveSettings();
5142
- 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) };
5143
5239
  }
5144
- const k = settings.exaKey || '';
5145
- return { ok: true, set: !!k, masked: k ? '••••••••' + k.slice(-4) : '' };
5146
5240
  });
5147
- ipcMain.handle('exa:clear', () => {
5148
- settings.exaKey = '';
5241
+ ipcMain.handle('obscura:setEnabled', (_e, v) => {
5242
+ settings.obscuraEnabled = v !== false;
5149
5243
  saveSettings();
5150
- setExaKey(null);
5151
- 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
+ }
5152
5259
  });
5153
5260
 
5154
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',
@@ -197,6 +197,7 @@
197
197
  it_reset_pair: 'Eşlemeyi Sıfırla',
198
198
  it_groups_on: 'WhatsApp gruplarında çalış',
199
199
  it_groups_mention: 'Sadece @mention edilince cevap ver (grupta botu etiketle)',
200
+ it_groups_seeall: 'Mention modunda grubun TÜM konuşmalarını oku (bağlam biriktirir, yine sadece @mention\'a cevap verir)',
200
201
  it_groups_all: 'Grubun her mesajına karış',
201
202
  it_allow_label: 'İzinli numaralar',
202
203
  it_name_ph: 'İsim (zorunlu)',
@@ -259,18 +260,30 @@
259
260
  ws_calls: 'çağrı',
260
261
  ws_token: 'token',
261
262
  ws_h2: 'Web Arama',
262
- 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.',
263
- ws_exa_label: 'Exa API anahtarı (exa.ai)',
264
- 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',
265
282
  ws_save: 'Kaydet',
266
283
  ws_clear: 'Anahtarı Sil',
267
284
  ws_note: 'Anahtar maskeli tutulur; yenisini yazıp kaydettiğinde eskisinin yerine geçer. Boş kaydet: mevcut anahtar korunur.',
268
- ws_status_set: 'Exa aktif — kayıtlı anahtar: ',
269
- ws_status_unset: 'Exa yapılandırılmadı — ücretsiz python çoklu-motor arama kullanılıyor.',
270
285
  ws_empty_toast: 'Anahtar boş — mevcut anahtar korundu',
271
- ws_saved_toast: 'Exa anahtarı kaydedildi',
272
286
  ws_fail_toast: 'Kaydedilemedi',
273
- ws_cleared_toast: 'Exa anahtarı silindi',
274
287
  ag_h2: 'Paralel Ajanlar',
275
288
  ag_sub: 'CEO\u2019nun devrettiği arka plan işleri — canlı izleme',
276
289
  ag_ceo_label: 'CEO modu — konuşan ajan iş yapmaz, sadece emir verir',
@@ -299,12 +312,11 @@
299
312
  us_where_on_toast: 'Açılış özeti açık',
300
313
  us_where_off_toast: 'Açılış özeti kapandı',
301
314
  ws_h2: 'Web Arama',
302
- 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.',
303
317
  ws_save: 'Kaydet',
304
318
  ws_clear: 'Anahtarı Sil',
305
319
  ws_key_note: 'Anahtar maskeli tutulur; yenisini yazıp kaydettiğinde eskisinin yerine geçer. Boş kaydet: mevcut anahtar korunur.',
306
- ws_active: 'Exa aktif — kayıtlı anahtar: ',
307
- ws_notcfg: 'Exa yapılandırılmadı — ücretsiz python çoklu-motor arama kullanılıyor.',
308
320
  ws_empty: 'Anahtar boş — mevcut anahtar korundu',
309
321
  // terminal & tarayıcı & mini
310
322
  term_clear: 'Temizle',
@@ -640,10 +652,10 @@
640
652
  lim_saved_toast: 'Limits saved',
641
653
  lim_unlimited: 'unlimited',
642
654
  tf_h2: 'TinyFish Search',
643
- 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.',
644
656
  tf_key_label: 'TinyFish API key (agent.tinyfish.ai/api-keys)',
645
657
  tf_status_set: 'TinyFish active — saved key: ',
646
- tf_status_unset: 'TinyFish off — using the old search chain.',
658
+ tf_status_unset: 'TinyFish off — the chain continues with the next engine.',
647
659
  tf_empty_toast: 'Key empty — current key kept',
648
660
  tf_saved_toast: 'TinyFish key saved',
649
661
  tf_cleared_toast: 'TinyFish key deleted',
@@ -726,6 +738,7 @@
726
738
  it_reset_pair: 'Reset pairing',
727
739
  it_groups_on: 'Work in WhatsApp groups',
728
740
  it_groups_mention: 'Reply only when @mentioned (tag the bot in the group)',
741
+ it_groups_seeall: 'In mention mode, read ALL group messages as context (still replies only when @mentioned)',
729
742
  it_groups_all: 'Respond to every group message',
730
743
  it_allow_label: 'Allowed numbers',
731
744
  it_name_ph: 'Name (required)',
@@ -788,18 +801,30 @@
788
801
  ws_calls: 'calls',
789
802
  ws_token: 'tokens',
790
803
  ws_h2: 'Web Search',
791
- 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.',
792
- ws_exa_label: 'Exa API key (exa.ai)',
793
- 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',
794
823
  ws_save: 'Save',
795
824
  ws_clear: 'Delete Key',
796
825
  ws_note: 'The key is kept masked; saving a new one replaces the old. Saving empty keeps the current key.',
797
- ws_status_set: 'Exa active — saved key: ',
798
- ws_status_unset: 'Exa not configured — using free python multi-engine search.',
799
826
  ws_empty_toast: 'Key empty — current key kept',
800
- ws_saved_toast: 'Exa key saved',
801
827
  ws_fail_toast: 'Could not save',
802
- ws_cleared_toast: 'Exa key deleted',
803
828
  ag_h2: 'Parallel Agents',
804
829
  ag_sub: 'Background tasks delegated by the CEO — live monitoring',
805
830
  ag_ceo_label: 'CEO mode — the talking agent does no work, only gives orders',
@@ -890,12 +915,11 @@
890
915
  us_where_on_toast: 'Startup summary on',
891
916
  us_where_off_toast: 'Startup summary off',
892
917
  ws_h2: 'Web Search',
893
- 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.',
894
920
  ws_save: 'Save',
895
921
  ws_clear: 'Delete Key',
896
922
  ws_key_note: 'Key is kept masked; saving a new one replaces the old. Save empty: existing key kept.',
897
- ws_active: 'Exa active — saved key: ',
898
- ws_notcfg: 'Exa not configured — free python multi-engine search is used.',
899
923
  ws_empty: 'Key empty — existing key kept',
900
924
  term_clear: 'Clear',
901
925
  term_ph: 'type a command, press Enter…',