beast-agent 1.9.0 → 2.0.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.
Files changed (52) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +130 -130
  3. package/bin/beast-agent.js +137 -137
  4. package/package.json +1 -1
  5. package/scripts/fix-electron.js +138 -138
  6. package/scripts/release.js +137 -131
  7. package/scripts/swap-electron.js +40 -40
  8. package/src/agent/agentdefs.js +122 -122
  9. package/src/agent/bots.js +580 -580
  10. package/src/agent/bus.js +389 -389
  11. package/src/agent/computeruse.js +200 -200
  12. package/src/agent/config.js +284 -284
  13. package/src/agent/discord.js +332 -332
  14. package/src/agent/engine.js +75 -10
  15. package/src/agent/kb.js +123 -123
  16. package/src/agent/llm.js +430 -430
  17. package/src/agent/logger.js +90 -90
  18. package/src/agent/mcp.js +427 -427
  19. package/src/agent/mem0.js +605 -605
  20. package/src/agent/memory.js +427 -427
  21. package/src/agent/mqueue.js +124 -124
  22. package/src/agent/pdf.js +20 -20
  23. package/src/agent/research.js +133 -133
  24. package/src/agent/scripts/news.py +113 -113
  25. package/src/agent/scripts/stealthsearch.py +30 -30
  26. package/src/agent/scripts/websearch.py +225 -225
  27. package/src/agent/searxng.js +325 -325
  28. package/src/agent/seeds/brainstorming/SKILL.md +90 -90
  29. package/src/agent/seeds/dispatching-parallel-agents/SKILL.md +120 -120
  30. package/src/agent/seeds/executing-plans/SKILL.md +60 -60
  31. package/src/agent/seeds/subagent-driven-development/SKILL.md +167 -167
  32. package/src/agent/seeds/systematic-debugging/SKILL.md +131 -131
  33. package/src/agent/seeds/test-driven-development/SKILL.md +152 -152
  34. package/src/agent/seeds/verification-before-completion/SKILL.md +63 -63
  35. package/src/agent/seeds/writing-plans/SKILL.md +162 -162
  36. package/src/agent/seeds/writing-skills/SKILL.md +229 -229
  37. package/src/agent/skills.js +652 -652
  38. package/src/agent/store.js +378 -378
  39. package/src/agent/telegram.js +155 -155
  40. package/src/agent/tokens.js +39 -39
  41. package/src/agent/usage.js +125 -125
  42. package/src/agent/watchers.js +312 -312
  43. package/src/agent/watext.js +80 -80
  44. package/src/agent/whatsapp.js +555 -555
  45. package/src/cron.js +255 -255
  46. package/src/main.js +348 -6
  47. package/src/preload.js +9 -0
  48. package/src/renderer/browserPreload.js +73 -73
  49. package/src/renderer/i18n.js +4 -0
  50. package/src/renderer/index.html +448 -409
  51. package/src/renderer/renderer.js +824 -41
  52. package/src/renderer/style.css +264 -5
@@ -1377,8 +1377,10 @@ class Engine {
1377
1377
 
1378
1378
  /* Beast Code (IDE paneli): VS Code hızında çalışsın diye SECMET prompt.
1379
1379
  OpenCode kodlama disiplini gömülü: bağlam → plan → küçük diff → doğrula.
1380
- Hafıza embedding araması, skills taraması, kişilik/kural blokları YOK
1381
- prompt kısa kalır ilk token hızlı, tur maliyeti düşük. */
1380
+ GENEL AMAÇLI kodlama ajanı web/mobil gibi tek stack'e zorlanmaz; stack'i
1381
+ projenin kendi dosyaları belirler. Hafıza embedding araması, skills
1382
+ taraması, kişilik/kural blokları YOK — prompt kısa kalır → ilk token
1383
+ hızlı, tur maliyeti düşük. */
1382
1384
  buildBcSystem(session) {
1383
1385
  const nowD = new Date();
1384
1386
  const localDate = nowD.toLocaleDateString('tr-TR');
@@ -1395,9 +1397,11 @@ class Engine {
1395
1397
  const proj = this._projectInstructions(session);
1396
1398
  return (
1397
1399
  'Sen BEAST CODE\u2019sun — IDE panelinde çalışan, OpenCode disiplinli hızlı bir kodlama ajanı. VS Code gibi çevik ol.\n' +
1400
+ 'GENEL AMAÇLI KODLAMA AJANISIN — tek bir proje türüne sınırlı değilsin: web, backend/API, mobil, masaüstü, CLI, kütüphane, script, oyun, veri, DevOps, gömülü… İstenen neyse o. İstenmeyen şeyi (ör. site değilken site) kendiliğinden ÜRETME.\n' +
1398
1401
  `Çalışma klasörü: ${(session && session.workspace) || this.workspace}\n` +
1399
1402
  `Yerel zaman: ${localDate} ${localTime}\n` +
1400
1403
  `${modeBlock}\n` +
1404
+ 'STACK TESPİTİ: işe başlarken workspace\u2019i tanı — package.json, go.mod, Cargo.toml, requirements.txt/pyproject.toml, pom.xml/build.gradle, *.csproj, pubspec.yaml, Gemfile, composer.json vb. işaret dosyalarına bak (list_dir + glob). Dili, framework\u2019ü, konvansiyonları ve build/test/run komutlarını PROJE belirler; her işte aynı teknolojiye itme, mevcut stack\u2019e uy.\n' +
1401
1405
  (proj
1402
1406
  ? '# PROJE TALİMATLARI (workspace AGENTS/CLAUDE/CONTEXT dosyalarından — daima uy)\n' + proj + '\n'
1403
1407
  : '') +
@@ -1405,12 +1409,11 @@ class Engine {
1405
1409
  '1) BAĞLAM: değiştirmeden önce ilgili dosyaları OKU — read_file satır numaralı döner (N: içerik); büyük dosyada devamını offset parametresiyle oku, ASLA baştan okuma; bir dosyayı aynı oturumda BİR KEZ okumak yeter — okuduğun içerik oturum SONUNA KADAR bağlamda KALIR, edit yaptıktan sonra dosyayı yeniden okuma YASAK (güncel durum = son okuma + kendi editlerin). İçerik araması için grep (regex), dosya adı için glob kullan; varsayım yapma, mevcut stili/konvansiyonu takip et.\n' +
1406
1410
  '2) PLAN: 2+ adımlı işlerde İLK EYLEM todo_write olsun (2-6 madde); her adım bitince status:"done" ile GÜNCELLE — liste bitene kadar iş bitmiş sayılmaz.\n' +
1407
1411
  '3) EDİT: VAR OLAN dosyada önce edit_file kullan (old_string/new_string ile yalnız ilgili bölümü değiştir; birden çok eşleşme varsa bağlam ekle ya da replace_all); write_file yalnız YENİ dosya ya da tam yeniden yazım için. Dosya işlemleri için ÖZEL ARAÇLARI kullan (edit_file/write_file/read_file/grep/glob); run_command terminal işlerindir (build, git, kurulum, paket) — dosya düzenlemeyi komut/scripte yedirme, edit_file ile yap. İlgisiz yeniden biçimleme/kayıp boşluk değişikliği YAPMA. edit_file/write_file sonucu additions/deletions döner ve değişiklik diske ANINDA uygulanır — doğrulamak için dosyayı TEKRAR OKUMA YASAK; sonraki editi önceki okuduğun içerik + kendi değişikliklerin üzerinden zincirle.\n' +
1408
- '4) DOĞRULA: edit sonrası mümkünse derle/test et/lint çalıştır (run_command); hata varsa DÜZELT ve TEKRAR dene (en fazla 2 doğrulama turu) — kırmızı bırakma. Doğrulama read_file ile DEĞİL run_command ile yapılır.\n' +
1412
+ '4) DOĞRULA: edit sonrası projenin KENDİ komutlarıyla derle/test/lint çalıştır (run_command: npm test, go test ./..., cargo test, pytest, mvn test vb. — hangisi geçerliyse); hata varsa DÜZELT ve TEKRAR dene (en fazla 2 doğrulama turu) — kırmızı bırakma. Doğrulama read_file ile DEĞİL run_command ile yapılır.\n' +
1409
1413
  '5) RAPOR: 1-3 satır — ne değişti + doğrulama sonucu (ör. "npm test ✓ 154/154"). Uzun açıklama yok.\n' +
1410
- 'CANLI ÖNİZLEME: panel Preview\u2019da DAHİLİ STATİK SUNUCUYU kendisi yönetir — statik site için sunucu başlatma DENEME (python -m http.server vs. GEREKMEZ); index.html\u2019i hazır bırak, kullanıcı Preview\u2019a basınca site sunucudan canlı açılır ve sana observe ile haber verilir. Yalnızca gerçek dev-server/build gerektiren projelerde (React/Vite/Next/Expo) kendi sunucunu BLOKLAMADAN arka planda başlat ve çalışan adresi (http://localhost:PORT) raporda yaz. file:// protokolü ASLA kullanılmaz.\n' +
1411
- 'MOBİL UYGULAMA: React Native/Expo ile yap — `npx create-expo-app <ad>` + `npx expo start` (BLOKLAMADAN arka planda; başlangıç çıktısında http://localhost:8081 görünür, panel Telefon Modunda canlı açar, dosya kaydında hot reload çalışır). APK: `npx expo prebuild` + android\\gradlew assembleDebug; iOS: Windows\u2019ta simülatör YOK — gerçek cihazda Expo Go (QR) veya EAS cloud build kullan.\n' +
1414
+ 'ÖNİZLEME: panel Preview\u2019da dahili statik sunucuyu KENDİSİ yönetir — statik HTML işinde sunucu başlatma (python -m http.server vs. GEREKMEZ); index.html\u2019i hazır bırak, kullanıcı Preview\u2019a basınca canlı açılır. Yalnız gerçek dev-server gerektiren projelerde (React/Vite/Next/Expo vb.) kendi sunucunu BLOKLAMADAN arka planda başlat ve çalışan adresi (http://localhost:PORT) raporda yaz. file:// protokolü ASLA kullanılmaz.\n' +
1412
1415
  'HIZ KURALLARI:\n' +
1413
- '- Kodu ve dosyaları DOĞRUDAN write_file ile yaz; basit dosya oluşturma/düzenleme için Python scripti yazma; python_run yalnız gerçek hesap/veri işleme gerekiyorsa.\n' +
1416
+ '- VAR OLAN dosyayı edit_file ile değiştir, YENİ dosyayı write_file ile yarat; basit dosya oluşturma/düzenleme için Python scripti yazma; python_run yalnız gerçek hesap/veri işleme gerekiyorsa.\n' +
1414
1417
  '- Bağımsız araç çağrılarını AYNI TURDA paralel ver (birden çok read_file tek turda).\n' +
1415
1418
  '- Dosyayı kullanıcı söylememişse list_dir ile yapıyı görüp kendin karar ver.\n' +
1416
1419
  '- Run_command PowerShell ortamında çalışır (Windows): KALICI oturum — cd ve $env değişkenleri çağrılar arasında korunur; büyük çıktıda tam çıktı geçici dosyaya düşer, read_file ile oku. shell:"bash" verirsen git-bash ile bash sözdizimi koşar.\n' +
@@ -1419,6 +1422,66 @@ class Engine {
1419
1422
  );
1420
1423
  }
1421
1424
 
1425
+ /* Beast Studio (video paneli): video YAPMA ve DÜZENLEME ajanı. Workspace'teki
1426
+ her şey video proje malzemesi olarak görülür (ham görüntü, ses, görsel,
1427
+ altyazı, çıktı). Medya işi ffprobe + ffmpeg ile yürütür; ikisi de app ile
1428
+ birlikte gelir (ffmpeg-static). Beast Code disiplini korunur: plan →
1429
+ uygula → doğrula. */
1430
+ buildStudioSystem(session) {
1431
+ const nowD = new Date();
1432
+ const localDate = nowD.toLocaleDateString('tr-TR');
1433
+ const localTime = String(nowD.getHours()).padStart(2, '0') + ':00';
1434
+ const mode = String((session && session.bcMode) || 'auto').toLowerCase();
1435
+ const modeBlock =
1436
+ mode === 'plan'
1437
+ ? 'ÇALIŞMA MODU: PLAN 🔍 — malzemeyi İNCELE (list_dir + ffprobe), KOMUT ÇALIŞTIRMA; adım adım MONTAJ PLANI ver (hangi dosya, hangi işlem, çıktı adı, nasıl doğrulanır). Kullanıcı /build deyince plan uygulanır.'
1438
+ : mode === 'build'
1439
+ ? 'ÇALIŞMA MODU: BUILD 🛠 — son montaj planını SOHBETTEKİ bağlamdan al ve UYGULA: ffmpeg işlerini çalıştır, çıktıları doğrula. Yeni plan açma; en fazla 1 cümlelik yön gösterimi + uygulama.'
1440
+ : 'ÇALIŞMA MODU: OTOMATİK ⚡ — önce 2-4 satırlık mini plan (todo_write), sonra hemen uygula + doğrula.';
1441
+ let ffPath = '';
1442
+ try { ffPath = String(require('ffmpeg-static') || ''); } catch {}
1443
+ let ffprobeHint = '';
1444
+ if (ffPath) {
1445
+ const ffDir = path.dirname(ffPath);
1446
+ ffprobeHint =
1447
+ `Medya araçları KURULU, kurmaya ÇALIŞMA:\n` +
1448
+ `- ffmpeg: "${ffPath}"\n` +
1449
+ `- ffprobe: "${path.join(ffDir, 'ffprobe' + (process.platform === 'win32' ? '.exe' : ''))}" (yoksa ffmpeg -i de meta verir)\n` +
1450
+ `Bunları run_command ile TAM YOLLA çağır (PATH'te değiller). Boşluklu yolları tırnakla.\n`;
1451
+ }
1452
+ const proj = this._projectInstructions(session);
1453
+ return (
1454
+ 'Sen BEAST STUDIO\u2019sun — video YAPMA ve DÜZENLEME ajanı. Sol paneldeki klasör senin stüdyondur.\n' +
1455
+ 'GÖREV ALANI: video üretme ve düzenleme — kesme/birleştirme (montaj), trim, crop, ölçek, döndürme, hız (slowmo/timelapse), geçiş efektleri, filtre/renk, altyazı basma (srt/ass burn-in), ses ekleme/değiştirme/ses seviyesi/ses temizleme, GIF, thumbnail, format dönüşümü — kısaca kullanıcının istediği HER türlü video/ses işi. Kod yazma projesi DEĞİLDİR — kod istenirse kullanıcı Beast Code\u2019a yönlendirilir.\n' +
1456
+ `Stüdyo klasörü: ${(session && session.workspace) || this.workspace}\n` +
1457
+ `Yerel zaman: ${localDate} ${localTime}\n` +
1458
+ `${modeBlock}\n` +
1459
+ 'KLASÖR FARKINDALIĞI: çalışma klasöründeki HER DOSYA video proje MALZEMESİDİR — ham görüntüler, sesler, müzikler, görseller, altyazılar, önceki çıktılar. İşe başlarken list_dir ile malzemeyi gör; isimden tür UYDURMA, ffprobe ile doğrula. Yeni klasör seçilirse (panelde Klasör seç) yeni proje başlar — oradaki malzemeye göre çalış.\n' +
1460
+ ffprobeHint +
1461
+ 'MEDYA ANALİZİ: video/ses dosyalarını read_file ile OKUMA (ikili dosya) — ffprobe ile süre, codec, çözünürlük, fps, ses kanalı, bit hızı bilgisini al: ffprobe -v error -show_entries format=duration,size:stream=codec_name,width,height,r_frame_rate,channels -of default=noprint_wrappers=1 "dosya"\n' +
1462
+ 'FFMPEG KURALLARI:\n' +
1463
+ '- Çıktıya HER ZAMAN -y ekle (üstüne yaz). Uzun işlerde stderr büyük olur; gerekiyorsa çıktıyı dosyaya düşürüp son 20 satırı oku.\n' +
1464
+ '- Varsayılan kodlama: video libx264 + crf 20, ses aac 192k, mp4 kapsayıcı; kullanıcı kalite belirtirse crf 16-18.\n' +
1465
+ '- Kesme: -ss BAŞ -to BİT (girişten önce hızlı arama için -ss\u2019yi -i\u2019den ÖNCE koy, yeniden kodlamalı kesin işlerde sonra da olur) -i girdi -t SÜRE.\n' +
1466
+ '- Birleştirme: uyumsuz kaynaklarda concat demuxer yerine filtre (concat=n=...:v=1:a=1) kullan veya önce hepsini aynı çözünürlük/fps/codece normalize et.\n' +
1467
+ '- Altyazı: subtitles="dosya.srt":force_style\u2026 (Windows yolunda escaping: C\\:/…); ses: -i ses -map 0:v -map 1:a -c:v copy -shortest.\n' +
1468
+ 'ÇIKTI DİSİPLİNİ: üretilen her dosyayı çalışma klasörü içindeki "output" klasörüne yaz (önce mkdir); dosya adları ASCII, boşluksuz ve anlamlı olsun (ör. output\/kesme-01.mp4). Ara dosyaları da output\/tmp altında tut, iş bitince temizle.\n' +
1469
+ 'WORKFLOW (her işte bu sıra):\n' +
1470
+ '1) BAĞLAM: list_dir + ffprobe ile malzeme envanteri (paralel çağrılar TEK turda).\n' +
1471
+ '2) PLAN: 2+ adımlı işlerde İLK EYLEM todo_write olsun (2-6 madde); her adım bitince status:"done" ile GÜNCELLE.\n' +
1472
+ '3) UYGULA: her ffmpeg işini run_command ile adım adım çalıştır; büyük işi tek dev komuta gömme — kırıp zincirle.\n' +
1473
+ '4) DOĞRULA: her adımda çıktı gerçekten oluştu mu — ffprobe ile süre/codec kontrolü ETMEDEN "tamam" DEME; hata varsa komutu düzeltip en fazla 2 kez TEKRAR DENE.\n' +
1474
+ '5) RAPOR: 1-3 satır — üretilen dosya(lar) + süre/boyut + ne yapıldı. Dosya yolunu MUTLAKA yaz (kullanıcı panelde oynatır).\n' +
1475
+ 'HIZ KURALLARI:\n' +
1476
+ '- Bağımsız ffprobe/ffmpeg çağrılarını AYNI TURDA paralel ver.\n' +
1477
+ '- run_command PowerShell ortamında çalışır (Windows): KALICI oturum — cd korunur.\n' +
1478
+ '- Görüntü üretimi/thumbnail: ffmpeg -ss ortası -i girdi -frames:v 1.\n' +
1479
+ '- Soru sorma; malzemeden emin olamadığında ffprobe\u2019la kendin çöz. Kullanıcı /plan /build /auto ile modu değiştirir.\n' +
1480
+ (proj ? '# PROJE TALİMATLARI (workspace AGENTS/CLAUDE/CONTEXT dosyalarından — daima uy)\n' + proj + '\n' : '') +
1481
+ FORMAT_RULES
1482
+ );
1483
+ }
1484
+
1422
1485
  async buildSystem(queryText, session) {
1423
1486
  /* BOT OTURUMU: non-admin bota bağlıysa global Beast hafızası HIÇ girmez —
1424
1487
  o botun kendi SOUL/USER/MEMORY dosyaları kullanılır (tam izolasyon) */
@@ -2854,9 +2917,11 @@ class Engine {
2854
2917
  const promptText = this._lastUserText(session);
2855
2918
  let system = session.bgJob
2856
2919
  ? this.buildBgSystem(session)
2857
- : session.bcCode
2858
- ? this.buildBcSystem(session)
2859
- : await this.buildSystem(promptText, session);
2920
+ : session.studio
2921
+ ? this.buildStudioSystem(session)
2922
+ : session.bcCode
2923
+ ? this.buildBcSystem(session)
2924
+ : await this.buildSystem(promptText, session);
2860
2925
  // Granül izin: oturumun yetki seviyesine göre araç seti daraltılır.
2861
2926
  // Çoklu izin (ör. web+read) seçiliyse kümeler BİRLEŞİR — hepsinin araçları açık olur.
2862
2927
  const perms = this.sessionPermFor(session.id);
@@ -3456,7 +3521,7 @@ class Engine {
3456
3521
  ajan "eski skillden daha kolay/better yol buldum" diyebilmesin, KARAR VERİP
3457
3522
  skilli GÜNCELLESİN. action: create (yeni) | update (mevcutu iyileştir) | none */
3458
3523
  const skills = require('./skills');
3459
- const existing = skills
3524
+ const existing = skills
3460
3525
  .scan()
3461
3526
  .map((s) => `- ${s.name}: ${(s.description || '').slice(0, 100)}`)
3462
3527
  .join('\n');
package/src/agent/kb.js CHANGED
@@ -1,123 +1,123 @@
1
- 'use strict';
2
-
3
- /* Beast knowledge store (#3 derin öğrenme katmanı)
4
- sqlite-vec yerine sıfır bağımlılık: JSON deposu + TF-IDF benzerlik.
5
- Kayıtlar kaynak taşır → kb_search sonucu citation'lı döner.
6
-
7
- Depo: %APPDATA%\beast\knowledge.json
8
- { items: [{ id, title, source, tags[], text, createdAt }] } */
9
-
10
- const fs = require('fs');
11
- const path = require('path');
12
- const { beastRoot } = require('./memory');
13
-
14
- function file() {
15
- return path.join(beastRoot(), 'knowledge.json');
16
- }
17
-
18
- let items = null;
19
-
20
- function load() {
21
- if (items) return;
22
- try {
23
- const raw = JSON.parse(fs.readFileSync(file(), 'utf8'));
24
- items = Array.isArray(raw.items) ? raw.items : [];
25
- } catch {
26
- items = [];
27
- }
28
- }
29
-
30
- function save() {
31
- try {
32
- fs.mkdirSync(beastRoot(), { recursive: true });
33
- const tmp = file() + '.tmp';
34
- fs.writeFileSync(tmp, JSON.stringify({ items }, null, 2));
35
- fs.renameSync(tmp, file());
36
- } catch {}
37
- }
38
-
39
- const KB_CAP = 2000;
40
-
41
- function uid() {
42
- return Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
43
- }
44
-
45
- /* memory.js ile aynı Türkçe duyarsız tokenleştirme */
46
- const TR_FOLD = { ç: 'c', ğ: 'g', ı: 'i', ö: 'o', ş: 's', ü: 'u', â: 'a', î: 'i', û: 'u' };
47
- function fold(s) {
48
- return String(s || '').toLowerCase().replace(/[çğıöşüâîû]/g, (ch) => TR_FOLD[ch] || ch);
49
- }
50
- function tokenize(q) {
51
- return (fold(q).match(/[a-z0-9_+#.]+/g) || []).filter((w) => w.length >= 3);
52
- }
53
-
54
- /** add(title, text, {source, tags}) → { ok, id } */
55
- function add(title, text, meta = {}) {
56
- load();
57
- const t = String(text || '').trim();
58
- if (!t) return { ok: false, error: 'metin boş' };
59
- if (items.length >= KB_CAP) items.splice(0, items.length - KB_CAP + 1); // en eskiyi düşür
60
- const it = {
61
- id: uid(),
62
- title: String(title || '').trim().slice(0, 120) || t.slice(0, 60),
63
- source: String(meta.source || '').trim().slice(0, 300) || 'agent',
64
- tags: Array.isArray(meta.tags) ? meta.tags.map((x) => String(x).slice(0, 40)).slice(0, 8) : [],
65
- text: t.slice(0, 20000),
66
- createdAt: new Date().toISOString(),
67
- };
68
- items.push(it);
69
- save();
70
- return { ok: true, id: it.id };
71
- }
72
-
73
- function get(id) {
74
- load();
75
- return items.find((x) => x.id === id) ? { ...items.find((x) => x.id === id) } : null;
76
- }
77
-
78
- function count() {
79
- load();
80
- return items.length;
81
- }
82
-
83
- /** TF-IDF benzerlik araması; sonuçlar citation alanıyla döner */
84
- function search(query, limit = 5) {
85
- load();
86
- const qTokens = tokenize(query);
87
- if (!qTokens.length || !items.length) return [];
88
- const N = items.length;
89
- const docs = items.map((it) => tokenize(it.title + ' ' + it.text));
90
- /* df */
91
- const df = new Map();
92
- for (const toks of docs) {
93
- for (const t of new Set(toks)) df.set(t, (df.get(t) || 0) + 1);
94
- }
95
- const scored = [];
96
- for (let i = 0; i < N; i++) {
97
- const toks = docs[i];
98
- if (!toks.length) continue;
99
- const tf = new Map();
100
- for (const t of toks) tf.set(t, (tf.get(t) || 0) + 1);
101
- let score = 0;
102
- for (const q of qTokens) {
103
- const f = tf.get(q) || 0;
104
- if (!f) continue;
105
- const idf = Math.log(1 + N / (1 + (df.get(q) || 0)));
106
- score += (f / toks.length) * idf;
107
- }
108
- /* tag/başlık eşleşmesine bonus */
109
- const hayT = fold(items[i].title + ' ' + (items[i].tags || []).join(' '));
110
- for (const q of qTokens) if (hayT.includes(q)) score += 0.35;
111
- if (score > 0) scored.push({ i, score });
112
- }
113
- scored.sort((a, b) => b.score - a.score);
114
- return scored.slice(0, Math.max(1, Math.min(limit, 10))).map(({ i, score }) => ({
115
- id: items[i].id,
116
- title: items[i].title,
117
- snippet: items[i].text.slice(0, 400),
118
- score: Number(score.toFixed(3)),
119
- citation: `[${items[i].title}] (${items[i].source}, ${String(items[i].createdAt).slice(0, 10)})`,
120
- }));
121
- }
122
-
123
- module.exports = { add, get, search, count, tokenize };
1
+ 'use strict';
2
+
3
+ /* Beast knowledge store (#3 derin öğrenme katmanı)
4
+ sqlite-vec yerine sıfır bağımlılık: JSON deposu + TF-IDF benzerlik.
5
+ Kayıtlar kaynak taşır → kb_search sonucu citation'lı döner.
6
+
7
+ Depo: %APPDATA%\beast\knowledge.json
8
+ { items: [{ id, title, source, tags[], text, createdAt }] } */
9
+
10
+ const fs = require('fs');
11
+ const path = require('path');
12
+ const { beastRoot } = require('./memory');
13
+
14
+ function file() {
15
+ return path.join(beastRoot(), 'knowledge.json');
16
+ }
17
+
18
+ let items = null;
19
+
20
+ function load() {
21
+ if (items) return;
22
+ try {
23
+ const raw = JSON.parse(fs.readFileSync(file(), 'utf8'));
24
+ items = Array.isArray(raw.items) ? raw.items : [];
25
+ } catch {
26
+ items = [];
27
+ }
28
+ }
29
+
30
+ function save() {
31
+ try {
32
+ fs.mkdirSync(beastRoot(), { recursive: true });
33
+ const tmp = file() + '.tmp';
34
+ fs.writeFileSync(tmp, JSON.stringify({ items }, null, 2));
35
+ fs.renameSync(tmp, file());
36
+ } catch {}
37
+ }
38
+
39
+ const KB_CAP = 2000;
40
+
41
+ function uid() {
42
+ return Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
43
+ }
44
+
45
+ /* memory.js ile aynı Türkçe duyarsız tokenleştirme */
46
+ const TR_FOLD = { ç: 'c', ğ: 'g', ı: 'i', ö: 'o', ş: 's', ü: 'u', â: 'a', î: 'i', û: 'u' };
47
+ function fold(s) {
48
+ return String(s || '').toLowerCase().replace(/[çğıöşüâîû]/g, (ch) => TR_FOLD[ch] || ch);
49
+ }
50
+ function tokenize(q) {
51
+ return (fold(q).match(/[a-z0-9_+#.]+/g) || []).filter((w) => w.length >= 3);
52
+ }
53
+
54
+ /** add(title, text, {source, tags}) → { ok, id } */
55
+ function add(title, text, meta = {}) {
56
+ load();
57
+ const t = String(text || '').trim();
58
+ if (!t) return { ok: false, error: 'metin boş' };
59
+ if (items.length >= KB_CAP) items.splice(0, items.length - KB_CAP + 1); // en eskiyi düşür
60
+ const it = {
61
+ id: uid(),
62
+ title: String(title || '').trim().slice(0, 120) || t.slice(0, 60),
63
+ source: String(meta.source || '').trim().slice(0, 300) || 'agent',
64
+ tags: Array.isArray(meta.tags) ? meta.tags.map((x) => String(x).slice(0, 40)).slice(0, 8) : [],
65
+ text: t.slice(0, 20000),
66
+ createdAt: new Date().toISOString(),
67
+ };
68
+ items.push(it);
69
+ save();
70
+ return { ok: true, id: it.id };
71
+ }
72
+
73
+ function get(id) {
74
+ load();
75
+ return items.find((x) => x.id === id) ? { ...items.find((x) => x.id === id) } : null;
76
+ }
77
+
78
+ function count() {
79
+ load();
80
+ return items.length;
81
+ }
82
+
83
+ /** TF-IDF benzerlik araması; sonuçlar citation alanıyla döner */
84
+ function search(query, limit = 5) {
85
+ load();
86
+ const qTokens = tokenize(query);
87
+ if (!qTokens.length || !items.length) return [];
88
+ const N = items.length;
89
+ const docs = items.map((it) => tokenize(it.title + ' ' + it.text));
90
+ /* df */
91
+ const df = new Map();
92
+ for (const toks of docs) {
93
+ for (const t of new Set(toks)) df.set(t, (df.get(t) || 0) + 1);
94
+ }
95
+ const scored = [];
96
+ for (let i = 0; i < N; i++) {
97
+ const toks = docs[i];
98
+ if (!toks.length) continue;
99
+ const tf = new Map();
100
+ for (const t of toks) tf.set(t, (tf.get(t) || 0) + 1);
101
+ let score = 0;
102
+ for (const q of qTokens) {
103
+ const f = tf.get(q) || 0;
104
+ if (!f) continue;
105
+ const idf = Math.log(1 + N / (1 + (df.get(q) || 0)));
106
+ score += (f / toks.length) * idf;
107
+ }
108
+ /* tag/başlık eşleşmesine bonus */
109
+ const hayT = fold(items[i].title + ' ' + (items[i].tags || []).join(' '));
110
+ for (const q of qTokens) if (hayT.includes(q)) score += 0.35;
111
+ if (score > 0) scored.push({ i, score });
112
+ }
113
+ scored.sort((a, b) => b.score - a.score);
114
+ return scored.slice(0, Math.max(1, Math.min(limit, 10))).map(({ i, score }) => ({
115
+ id: items[i].id,
116
+ title: items[i].title,
117
+ snippet: items[i].text.slice(0, 400),
118
+ score: Number(score.toFixed(3)),
119
+ citation: `[${items[i].title}] (${items[i].source}, ${String(items[i].createdAt).slice(0, 10)})`,
120
+ }));
121
+ }
122
+
123
+ module.exports = { add, get, search, count, tokenize };