beast-agent 2.6.3 → 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 +1 -1
- package/src/agent/perception.js +82 -4
- package/src/main.js +19 -3
package/package.json
CHANGED
package/src/agent/perception.js
CHANGED
|
@@ -27,6 +27,7 @@ const DEFAULTS = {
|
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
const EVENT_CAP = 250; // depo tavanı — eskiler düşer
|
|
30
|
+
const EVENT_TTL = 24 * 3600 * 1000; // son 24 saat penceresi — dışındakiler silinir
|
|
30
31
|
const SEEN_CAP = 800; // dedup parmakizi havuzu
|
|
31
32
|
const SEEN_TTL = 6 * 3600 * 1000; // aynı olay 6 saat içinde tekrar gelirse yut
|
|
32
33
|
const FILTER_MAX = 12; // tek filtre çağrısında en fazla olay
|
|
@@ -303,6 +304,35 @@ function topicKey(type, title) {
|
|
|
303
304
|
return normTitle(type + ' ' + title).split(' ').slice(0, 5).join(' ');
|
|
304
305
|
}
|
|
305
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
|
+
|
|
306
336
|
function uid() {
|
|
307
337
|
return Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
|
|
308
338
|
}
|
|
@@ -515,6 +545,41 @@ async function runCycle({ cfg, signals, llmFilter, now = new Date(), log = () =>
|
|
|
515
545
|
for (const k of seenKeys.slice(0, seenKeys.length - SEEN_CAP)) delete st.seen[k];
|
|
516
546
|
}
|
|
517
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
|
+
|
|
518
583
|
/* UCUZ FİLTRE: tüm adaylar TEK toplu çağrıda (maliyet freni). Çökerse deterministik.
|
|
519
584
|
İlgi alanları: elle girilen + sohbetlerden öğrenilenler; son konuşmalar bağlam. */
|
|
520
585
|
const interests = combinedInterests(cfg);
|
|
@@ -531,7 +596,6 @@ async function runCycle({ cfg, signals, llmFilter, now = new Date(), log = () =>
|
|
|
531
596
|
}
|
|
532
597
|
if (!graded || !graded.size) log('filtre: deterministik puanlama');
|
|
533
598
|
|
|
534
|
-
const counts = { ignored: 0, stored: 0, queued: 0, dup: dups, raw: raws.length };
|
|
535
599
|
const actions = [];
|
|
536
600
|
for (const ev of pending) {
|
|
537
601
|
const g = graded && graded.get(String(ev.id).toLowerCase());
|
|
@@ -585,16 +649,24 @@ async function runCycle({ cfg, signals, llmFilter, now = new Date(), log = () =>
|
|
|
585
649
|
counts.queued = kept.length;
|
|
586
650
|
|
|
587
651
|
/* 'yok sayıldı' olaylar depoda TUTULMAZ — son olaylar listesi temiz kalır
|
|
588
|
-
|
|
652
|
+
(eski depoda kalan ignored'lar da bu filtreyle bir temizlikte silinir) */
|
|
589
653
|
st.events = st.events.filter((e) => e.status !== 'ignored');
|
|
590
654
|
|
|
655
|
+
/* SON 24 SAAT penceresi: daha eski olaylar depodan TAMAMEN silinir —
|
|
656
|
+
"son olaylar" listesi taze kalır (dedup/cooldown ayrı yapılarda, etkilenmez) */
|
|
657
|
+
const minTs = now.getTime() - EVENT_TTL;
|
|
658
|
+
st.events = st.events.filter((e) => {
|
|
659
|
+
const t = Date.parse(e.ts);
|
|
660
|
+
return !Number.isFinite(t) || t >= minTs;
|
|
661
|
+
});
|
|
662
|
+
|
|
591
663
|
/* depo tavanı */
|
|
592
664
|
if (st.events.length > EVENT_CAP) st.events = st.events.slice(st.events.length - EVENT_CAP);
|
|
593
665
|
|
|
594
666
|
st.lastRunAt = now.toISOString();
|
|
595
667
|
saveState(st);
|
|
596
668
|
log(
|
|
597
|
-
'cycle tamam: raw=' + counts.raw + ' dup=' + dups +
|
|
669
|
+
'cycle tamam: raw=' + counts.raw + ' dup=' + dups + ' similar=' + counts.similar +
|
|
598
670
|
' ignored=' + counts.ignored + ' stored=' + counts.stored + ' queued=' + counts.queued +
|
|
599
671
|
' (' + (Date.now() - t0) + 'ms)'
|
|
600
672
|
);
|
|
@@ -622,7 +694,13 @@ function markNotified(id, level, text, cooldownMin) {
|
|
|
622
694
|
function listEvents(limit) {
|
|
623
695
|
const st = loadState();
|
|
624
696
|
const n = Math.max(1, Math.min(200, Number(limit) || 60));
|
|
625
|
-
|
|
697
|
+
/* yalnız son 24 saat — depo temizliği döngüye kadar da liste taze dönsün */
|
|
698
|
+
const minTs = Date.now() - EVENT_TTL;
|
|
699
|
+
const fresh = st.events.filter((e) => {
|
|
700
|
+
const t = Date.parse(e.ts);
|
|
701
|
+
return !Number.isFinite(t) || t >= minTs;
|
|
702
|
+
});
|
|
703
|
+
return fresh.slice(-n).reverse().map((e) => ({
|
|
626
704
|
id: e.id,
|
|
627
705
|
ts: e.ts,
|
|
628
706
|
source: e.source,
|
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);
|
|
6529
|
-
olması için şart) —
|
|
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,
|
|
6558
|
+
Promise.resolve(sendWaSafe(jid, outWa))
|
|
6543
6559
|
.then(() => empatiInjectToSession(ensureWaSession(jid), inject))
|
|
6544
6560
|
.catch(() => {})
|
|
6545
6561
|
);
|