beast-agent 2.6.4 → 2.6.5

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": "2.6.4",
4
+ "version": "2.6.5",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
@@ -304,6 +304,35 @@ function topicKey(type, title) {
304
304
  return normTitle(type + ' ' + title).split(' ').slice(0, 5).join(' ');
305
305
  }
306
306
 
307
+ /* ---------- benzer haber dedup (kelime-kümesi benzerliği) ----------
308
+ Aynı olayın farklı kaynak varyasyonları farklı başlıkla gelir → fingerprint
309
+ kaçırır ("X yaptırım" / "ABD X'e yeni yaptırım"). Bu katman: TR fold'lu
310
+ stopword temizli kelime kümeleriyle containment/Jaccard benzerliği. */
311
+
312
+ const SIM_CONTAIN = 0.6; /* kısa başlık, uzun varyantın alt kümesiyse */
313
+ const SIM_JACCARD = 0.55; /* genel örtüşme eşiği */
314
+
315
+ const TITLE_STOP = new Set(
316
+ ('ve veya ya ile için ama fakat da de ki mi mu mu mü bir bu şu o en daha çok az olarak gibi sonra önce yeni son sonucunda ' +
317
+ 'the a an of in on to for and or is are was were be been at by with as it its this that from after before new').split(' ')
318
+ );
319
+
320
+ function titleTokens(t) {
321
+ const words = normTitle(String(t || '')).split(' ').filter((w) => w.length > 1 && !TITLE_STOP.has(w));
322
+ return new Set(words.slice(0, 24));
323
+ }
324
+
325
+ function tokensSimilar(a, b) {
326
+ if (!a || !b || !a.size || !b.size) return false;
327
+ const min = Math.min(a.size, b.size);
328
+ if (min < 2) return false; /* tek anahtar kelimelik başlık güvenilir değil */
329
+ let inter = 0;
330
+ for (const t of a) if (b.has(t)) inter++;
331
+ if (!inter) return false;
332
+ if (inter / min >= SIM_CONTAIN) return true;
333
+ return inter / (a.size + b.size - inter) >= SIM_JACCARD;
334
+ }
335
+
307
336
  function uid() {
308
337
  return Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
309
338
  }
@@ -516,6 +545,41 @@ async function runCycle({ cfg, signals, llmFilter, now = new Date(), log = () =>
516
545
  for (const k of seenKeys.slice(0, seenKeys.length - SEEN_CAP)) delete st.seen[k];
517
546
  }
518
547
 
548
+ /* FAZ 1 — döngü içi benzer birleştirme: aynı haberin farklı kaynak
549
+ varyasyonları TEK adaya iner (kaynaklar birleşir, LLM'e tekrar gitmez) */
550
+ const mergedPending = [];
551
+ let similar = 0;
552
+ for (const ev of pending) {
553
+ const tk = titleTokens(ev.title);
554
+ const twin = mergedPending.find((m) => tokensSimilar(m._tk, tk));
555
+ if (twin) {
556
+ if (!twin.sources.includes(ev.source)) twin.sources.push(ev.source);
557
+ similar++;
558
+ continue;
559
+ }
560
+ ev._tk = tk;
561
+ mergedPending.push(ev);
562
+ }
563
+ pending.splice(0, pending.length, ...mergedPending);
564
+
565
+ /* FAZ 2 — bildirim geçmişine karşı: son 24 saatte BENZER bir haber zaten
566
+ BİLDİRİLDİYSE aday sönüklenir (aynı haberin 3 varyasyon spam'i olmasın) */
567
+ const counts = { ignored: 0, stored: 0, queued: 0, dup: dups, raw: raws.length, similar };
568
+ const passable = [];
569
+ for (const ev of pending) {
570
+ const twin = st.events.find(
571
+ (s) => s.status === 'notified' && tokensSimilar(titleTokens(s.title), ev._tk)
572
+ );
573
+ if (twin) {
574
+ ev.status = 'ignored';
575
+ ev.reason = 'benzer haber zaten bildirildi: ' + String(twin.title || '').slice(0, 50);
576
+ counts.similar++;
577
+ continue;
578
+ }
579
+ passable.push(ev);
580
+ }
581
+ pending.splice(0, pending.length, ...passable);
582
+
519
583
  /* UCUZ FİLTRE: tüm adaylar TEK toplu çağrıda (maliyet freni). Çökerse deterministik.
520
584
  İlgi alanları: elle girilen + sohbetlerden öğrenilenler; son konuşmalar bağlam. */
521
585
  const interests = combinedInterests(cfg);
@@ -532,7 +596,6 @@ async function runCycle({ cfg, signals, llmFilter, now = new Date(), log = () =>
532
596
  }
533
597
  if (!graded || !graded.size) log('filtre: deterministik puanlama');
534
598
 
535
- const counts = { ignored: 0, stored: 0, queued: 0, dup: dups, raw: raws.length };
536
599
  const actions = [];
537
600
  for (const ev of pending) {
538
601
  const g = graded && graded.get(String(ev.id).toLowerCase());
@@ -603,7 +666,7 @@ async function runCycle({ cfg, signals, llmFilter, now = new Date(), log = () =>
603
666
  st.lastRunAt = now.toISOString();
604
667
  saveState(st);
605
668
  log(
606
- 'cycle tamam: raw=' + counts.raw + ' dup=' + dups +
669
+ 'cycle tamam: raw=' + counts.raw + ' dup=' + dups + ' similar=' + counts.similar +
607
670
  ' ignored=' + counts.ignored + ' stored=' + counts.stored + ' queued=' + counts.queued +
608
671
  ' (' + (Date.now() - t0) + 'ms)'
609
672
  );
package/src/main.js CHANGED
@@ -6420,6 +6420,20 @@ function empatiSourceLinePlain(ev) {
6420
6420
  return src ? '🔗 kaynak: ' + cap(src, 80) : '';
6421
6421
  }
6422
6422
 
6423
+ /* WhatsApp: kullanıcı tercihi — ham URL GÖRÜNMEZ, haber başlığı gider.
6424
+ (WA düz metinde başlık-linki desteklemez; gerçek link yine de ajanın
6425
+ BAĞLAM bloğunda durur — "linki at" derse model oradan paylaşıır.) */
6426
+ function empatiSourceLineWa(ev) {
6427
+ if (!ev) return '';
6428
+ const oneLine = (s) =>
6429
+ String(s || '').replace(/\s*\n+\s*/g, ' ').replace(/\s{2,}/g, ' ').trim();
6430
+ const ELLIPSIS = '...';
6431
+ const cap = (s, max) =>
6432
+ oneLine(s).length > max ? oneLine(s).slice(0, max - ELLIPSIS.length) + ELLIPSIS : oneLine(s);
6433
+ const title = oneLine(ev.title || ev.source || '').trim();
6434
+ return title ? '🔗 ' + cap(title, 80) : '';
6435
+ }
6436
+
6423
6437
  function empatiInjectToSession(sid, out) {
6424
6438
  try {
6425
6439
  if (!sid || engine.isBusy(sid)) return; /* tur ortasında geçmişi karıştırma — bildirim kanala zaten gitti */
@@ -6525,12 +6539,14 @@ function empatiDesktopSid() {
6525
6539
  Hiçbir entegrasyon yazılamazsa masaüstü chat UI (toast) kalır. */
6526
6540
  function empatiNotify(text, ev) {
6527
6541
  const cfg = empatiCfg();
6528
- /* desktop + Discord: başlık-linki (markdown); WA/TG: ham URL (tıklanabilir
6529
- olması için şart) — kanallar kendi biçimini alır */
6542
+ /* desktop + Discord: başlık-linki (markdown); TG: ham URL (tıklanabilir
6543
+ olması için şart); WA: yalnız başlık (kullanıcı tercihi ham URL yok) */
6530
6544
  const src = empatiSourceLine(ev);
6531
6545
  const srcPlain = empatiSourceLinePlain(ev);
6546
+ const srcWa = empatiSourceLineWa(ev);
6532
6547
  const out = PROACTIVE_MARK + '\n' + text + (src ? '\n' + src : '');
6533
6548
  const outPlain = PROACTIVE_MARK + '\n' + text + (srcPlain ? '\n' + srcPlain : '');
6549
+ const outWa = PROACTIVE_MARK + '\n' + text + (srcWa ? '\n' + srcWa : '');
6534
6550
  const inject = empatiInjectText(ev, out);
6535
6551
  const senders = [];
6536
6552
  const tryWa = () => {
@@ -6539,7 +6555,7 @@ function empatiNotify(text, ev) {
6539
6555
  if (own && wa && wa.connected) {
6540
6556
  const jid = own + '@s.whatsapp.net';
6541
6557
  senders.push(() =>
6542
- Promise.resolve(sendWaSafe(jid, outPlain))
6558
+ Promise.resolve(sendWaSafe(jid, outWa))
6543
6559
  .then(() => empatiInjectToSession(ensureWaSession(jid), inject))
6544
6560
  .catch(() => {})
6545
6561
  );