beast-agent 2.4.5 → 2.5.1

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.4.5",
4
+ "version": "2.5.1",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
@@ -1094,6 +1094,9 @@ class Engine {
1094
1094
  session.isBotDm = true;
1095
1095
  session.dmA = String(rec.a || '');
1096
1096
  session.dmB = String(rec.b || '');
1097
+ } else if (rec.t === 'bcws') {
1098
+ /* Beast Code oturumu: hangi çalışma klasörüne bağlı */
1099
+ session.bcWs = String(rec.ws || '');
1097
1100
  } else if (rec.t === 'msg') {
1098
1101
  delete rec.t;
1099
1102
  session.messages.push(rec);
@@ -1253,6 +1256,42 @@ class Engine {
1253
1256
  return out.slice(0, 100);
1254
1257
  }
1255
1258
 
1259
+ /* Beast Code panel oturumları: bgTitle 'Beast Code' olan gizli oturumlar.
1260
+ Soldaki dosya panelinin sohbet geçmişi listesi bunları gösterir —
1261
+ ana sohbet listesinden (listSessions) TAMAMEN AYRIDIR. */
1262
+ listBcSessions(limit = 100) {
1263
+ let files = [];
1264
+ try {
1265
+ files = fs.readdirSync(this.sessionsDir).filter((f) => f.endsWith('.jsonl'));
1266
+ } catch {}
1267
+ const out = [];
1268
+ for (const f of files) {
1269
+ const id = f.replace(/\.jsonl$/, '');
1270
+ let s;
1271
+ try { s = this._load(id); } catch { continue; }
1272
+ if (s.bgTitle !== 'Beast Code') continue; // yalnız Beast Code oturumları
1273
+ let title = '';
1274
+ for (const m of s.messages) {
1275
+ if (m.role !== 'user') continue;
1276
+ const t = Array.isArray(m.content)
1277
+ ? m.content.filter((p) => p && p.type === 'text').map((p) => String(p.text || '')).join(' ')
1278
+ : String(m.content || '');
1279
+ title = t.replace(/\s+/g, ' ').trim().slice(0, 64);
1280
+ if (title) break;
1281
+ }
1282
+ out.push({
1283
+ id,
1284
+ title: title || 'Beast Code oturumu',
1285
+ updatedAt: s.updatedAt,
1286
+ createdAt: s.createdAt,
1287
+ count: s.messages.length,
1288
+ ws: s.bcWs || '',
1289
+ });
1290
+ }
1291
+ out.sort((a, b) => String(b.updatedAt).localeCompare(String(a.updatedAt)));
1292
+ return out.slice(0, Math.max(1, Math.min(200, Number(limit) || 100)));
1293
+ }
1294
+
1256
1295
  createSession() {
1257
1296
  const id = uid();
1258
1297
  const code = this._newCode();
@@ -3704,6 +3743,9 @@ const skills = require('./skills');
3704
3743
  clearTimeout(kill);
3705
3744
  }
3706
3745
  };
3746
+ /* Empati Loop ilgi çıkarımı: günde bir, konuşma hafızasından LLM ile
3747
+ (kelime-frekans öğrenici kaldırıldı — çöp etiket üretiyordu) */
3748
+ const perception = require('./perception');
3707
3749
  return await nightref.run({
3708
3750
  llm,
3709
3751
  memory,
@@ -3712,6 +3754,10 @@ const skills = require('./skills');
3712
3754
  sinceIso,
3713
3755
  manual,
3714
3756
  now: new Date(),
3757
+ interests: {
3758
+ prompt: () => perception.interestPrompt(),
3759
+ apply: (raw) => perception.applyInferredInterests(raw),
3760
+ },
3715
3761
  log: (msg) => log.info('gece-yansima', msg),
3716
3762
  });
3717
3763
  } finally {
@@ -354,6 +354,38 @@ async function run(deps) {
354
354
  report.notes.push('kayıt sayısı az (' + entriesNow.length + ') — sıkılaştırma atlandı');
355
355
  }
356
356
 
357
+ /* 3b) ilgi alanı çıkarımı (Empati Loop, günde bir): kelime-frekans öğrenme
358
+ YOK — model birikmiş konuşma hafızasını okuyup gerçek konu etiketleri
359
+ üretir (prompt/apply perception modülünden gelir) */
360
+ report.interests = { updated: false, count: 0, labels: [] };
361
+ if (
362
+ !noLlm &&
363
+ deps.interests &&
364
+ typeof deps.interests.prompt === 'function' &&
365
+ typeof deps.interests.apply === 'function'
366
+ ) {
367
+ try {
368
+ const ip = deps.interests.prompt();
369
+ if (ip) {
370
+ const raw = await deps.llm(ip);
371
+ const ir = deps.interests.apply(raw);
372
+ if (ir && ir.ok) {
373
+ report.interests = {
374
+ updated: true,
375
+ count: ir.count || (Array.isArray(ir.labels) ? ir.labels.length : 0),
376
+ labels: Array.isArray(ir.labels) ? ir.labels : [],
377
+ };
378
+ } else {
379
+ report.notes.push('ilgi çıkarımı: model geçerli etiket üretmedi — mevcut liste korundu');
380
+ }
381
+ } else {
382
+ report.notes.push('ilgi çıkarımı: yeterli konuşma kaydı yok — atlandı');
383
+ }
384
+ } catch (e) {
385
+ report.errors.push('ilgi çıkarımı: ' + String((e && e.message) || e).slice(0, 120));
386
+ }
387
+ }
388
+
357
389
  /* 4) bağlam raporu + journal dosyası */
358
390
  const scopeLabel = deps.sinceIso
359
391
  ? dayKey(deps.sinceIso) + ' ' + String(deps.sinceIso).slice(11, 16) + ' → ' + dayKey(now) + ' ' + String(now.toTimeString ? now.toTimeString().slice(0, 5) : '')
@@ -365,6 +397,9 @@ async function run(deps) {
365
397
  '## Bugün öğrendiklerim\n' +
366
398
  (report.learnings.length ? report.learnings.map((l) => '- ' + l).join('\n') + '\n' : '- (kayıt yok)\n') +
367
399
  (report.factsAdded ? '\nHafızaya eklenen kalıcı bilgi: ' + report.factsAdded + ' madde\n' : '') +
400
+ (report.interests && report.interests.updated
401
+ ? '\n## İlgi alanları (günlük çıkarım)\n' + report.interests.labels.map((l) => '- ' + l).join('\n') + '\n'
402
+ : '') +
368
403
  '\n## Bellek bakımı\n' +
369
404
  '- Kayıt: ' + m.before + ' → ' + m.after + ' (düşülen ' + m.dropped + ', ' + m.merged + ' kayıt birleşti)\n' +
370
405
  '- Boyut: ' + m.charsBefore + ' → ' + m.charsAfter + ' char (~' + m.tokensSaved + ' token tasarruf)\n' +
@@ -21,6 +21,7 @@ const DEFAULTS = {
21
21
  notifyTarget: '', // bildirim hedefi: '' = bağlı entegrasyonlar | whatsapp | telegram | discord
22
22
  filterModel: '', // ucuz filtre modeli ('provider::model'); boşsa ana model
23
23
  interests: '', // kullanıcı ilgi alanları (relevance ağırlığı)
24
+ behavior: '', // AJAN DAVRANIŞI: proaktif mesajlarda nasıl konuşacağı (kullanıcı yazar)
24
25
  newsTopics: '', // Google News RSS sorguları (virgülle)
25
26
  weights: { importance: 0.40, relevance: 0.25, urgency: 0.20, novelty: 0.15 },
26
27
  };
@@ -56,6 +57,7 @@ function mergeCfg(raw) {
56
57
  : '',
57
58
  filterModel: String(r.filterModel || '').trim(),
58
59
  interests: String(r.interests || '').slice(0, 400),
60
+ behavior: String(r.behavior || '').slice(0, 800),
59
61
  newsTopics: String(r.newsTopics || '').slice(0, 400),
60
62
  weights: {
61
63
  importance: clampW(w.importance, DEFAULTS.weights.importance),
@@ -94,10 +96,11 @@ function saveState(st) {
94
96
  }
95
97
 
96
98
  /* ---------- empati hafızası: sohbet kaydı + öğrenilen ilgi alanları ----------
97
- Sohbet akışı (chat/WA/TG/DC) main tarafından buraya düşürülür; kullanıcı
98
- mesajlarından anlamlı kelimeler toplanır, sık geçenler "öğrenilen ilgi
99
- alanı" olur. Tarama filtresi ve bildirim mesajı bu hafızayla kişiselleşir:
100
- elle girilen ilgi alanları + öğrenilenler birlikte prompta girer. */
99
+ Sohbet akışı (chat/WA/TG/DC) main tarafından buraya düşürülür. İlgi
100
+ alanları artık KELİME SAYIMIYLA değil, gece yansımasında (günde bir)
101
+ modelin konuşma hafızasından LLM ÇIKARIMIYLA üretilir. Tarama filtresi
102
+ ve bildirim mesajı bu hafızayla kişiselleşir: elle girilen ilgi alanları
103
+ + çıkarılanlar birlikte prompta girer. */
101
104
 
102
105
  const CHAT_CAP = 120; // son sohbet kaydı tavanı
103
106
  const INTEREST_CAP = 24; // öğrenilen ilgi etiketi tavanı
@@ -109,22 +112,6 @@ function memFold(s) {
109
112
  return String(s || '').toLowerCase().replace(/[çğıöşüâîû]/g, (ch) => TR_FOLD_MEM[ch] || ch);
110
113
  }
111
114
 
112
- /* gereksiz kelimeler — ilgi alanı sayılmaz. Set FOLD'LU kurulur: kelimeler
113
- zaten memFold ile ASCII'ye indirgendikten sonra arandığı için 'bugün' gibi
114
- stopword'ler fold'lu haliyle ('bugun') listede olmalı. */
115
- const MEM_STOP_RAW =
116
- 'the and for with this that have from was are were been will would could should ' +
117
- 'bir bu şu o ve ile için gibi ama fakat çok daha en ne mi mı mu mü de da ki ise ya ' +
118
- 'veya yani bana bize sana ona beni bizi seni onun benim bizim senin onları var yok ' +
119
- 'olur olan olarak nasıl neden niye şey şeyi her hem acaba lazım gerek evet hayır ' +
120
- 'tamam tmm kanka abi hocam merhaba selam hey lütfen teşekkür sağol eyvallah bugün ' +
121
- 'yarın dün şimdi sonra önce hala henüz yine tekrar biraz az kadar çünkü eğer ' +
122
- 'yapabilir misin musun isterim istiyorum lazım gerekiyor beast asistan önemli ' +
123
- 'hakkında üzerine konuştuk konuşuyoruz konuşmak kendi aynı başka diğer belki ' +
124
- 'sanırım galiba gerekli olsun olmuyor oluyor bakalım biriyle hangi kim nerede';
125
-
126
- const MEM_STOP = new Set(MEM_STOP_RAW.split(/\s+/).map((w) => memFold(w)).filter(Boolean));
127
-
128
115
  function chatFile() {
129
116
  return path.join(beastRoot(), 'perception', 'chatmem.json');
130
117
  }
@@ -132,12 +119,22 @@ function chatFile() {
132
119
  function loadChatMem() {
133
120
  try {
134
121
  const r = JSON.parse(fs.readFileSync(chatFile(), 'utf8'));
135
- return {
122
+ const cm = {
136
123
  chats: Array.isArray(r.chats) ? r.chats : [],
137
124
  learned: r.learned && typeof r.learned === 'object' ? r.learned : {},
125
+ learnedSource: String(r.learnedSource || ''),
126
+ learnedAt: r.learnedAt || null,
138
127
  };
128
+ /* TEK SEFERLİK GÖÇ: eski kelime-frekans öğrenici çöp etiket üretiyordu
129
+ ("whatsapp", "gonderen", telefon numarası…). İlgi alanları artık gece
130
+ yansımasında LLM çıkarımı — eski kayıtlar ilk yüklemede temizlenir. */
131
+ if (cm.learnedSource !== 'llm' && Object.keys(cm.learned).length) {
132
+ cm.learned = {};
133
+ try { saveChatMem(cm); } catch {}
134
+ }
135
+ return cm;
139
136
  } catch {
140
- return { chats: [], learned: {} };
137
+ return { chats: [], learned: {}, learnedSource: '', learnedAt: null };
141
138
  }
142
139
  }
143
140
 
@@ -165,34 +162,72 @@ function rememberConversation(text, channel) {
165
162
  }
166
163
  }
167
164
 
168
- /* Kullanıcı mesajından ilgi alanı öğren: fold + stopword temizliği, sayaca yaz */
169
- function learnInterests(text) {
165
+ /* ---------- ilgi alanı çıkarımı (gece yansımasında, günde bir, LLM) ----------
166
+ Kelime-frekans sayımı YOK — taşıma etiketleri ("gönderen", kanal adı,
167
+ telefon) ve rastgele kelimeler ilgi sanılıyordu. Bunun yerine biriken
168
+ konuşma hafızasını model okur, gerçek KONU etiketleri çıkarır. */
169
+
170
+ const INTEREST_INFER_MAX_CHATS = 80;
171
+
172
+ function interestPrompt() {
173
+ const cm = loadChatMem();
174
+ const items = cm.chats.slice(-INTEREST_INFER_MAX_CHATS);
175
+ if (items.length < 5) return ''; // veri yetersiz — çıkarıma değmez
176
+ const lines = items.map((c) => '- [' + (c.ch || 'sohbet') + '] ' + c.t).join('\n').slice(0, 9000);
177
+ return (
178
+ 'Sen bir kullanıcı profili çıkarıcısısın. Aşağıdaki son sohbet kayıtlarından ' +
179
+ 'KULLANICININ GERÇEK İLGİ ALANLARINI çıkar.\n' +
180
+ 'KURALLAR:\n' +
181
+ '- 3-12 etiket; her biri 1-3 kelimelik KONU/ALAN adı (Türkçe) — örn. "kripto para", "yapay zeka", "web geliştirme", "futbol"\n' +
182
+ '- Yalnız kullanıcının gerçekten ilgilendiği/tekrar konuştuğu konular; tek seferlik rastgele kelimeler ve sohbet dolguları ETİKET DEĞİL\n' +
183
+ '- Kişi adı, telefon numarası, kanal/uygulama adı (whatsapp, telegram), hitap sözleri (kanka, abi), istek fiilleri ("yap", "ara", "gönder") KESİNLİKLE etiket olamaz\n' +
184
+ '- Emin olamadığın konuyu yazma; az ama doğru etiket — uydurma yok\n' +
185
+ 'SADECE JSON dön: {"interests":["...","..."]}\n\n' +
186
+ '# SON KONUŞMALAR (beast = asistanın cevabı, sohbet/empati = kullanıcı tarafı)\n' + lines
187
+ );
188
+ }
189
+
190
+ /* LLM cevabından etiket listesi: sert temizlik + dedupe (fold'lu) + tavan */
191
+ function parseInterestJson(raw) {
170
192
  try {
171
- const toks = (memFold(text).match(/[a-z0-9_+#.]{3,}/g) || [])
172
- .filter((w) => !MEM_STOP.has(w) && !/^\d+$/.test(w))
173
- .slice(0, 12);
174
- if (!toks.length) return false;
175
- const cm = loadChatMem();
176
- const now = Date.now();
177
- for (const w of toks) {
178
- const cur = cm.learned[w] || { c: 0, at: 0 };
179
- cur.c++;
180
- cur.at = now;
181
- cm.learned[w] = cur;
182
- }
183
- /* tavana in: en sık + en taze etiketler kalır */
184
- const keys = Object.keys(cm.learned);
185
- if (keys.length > INTEREST_CAP) {
186
- keys.sort((a, b) => (cm.learned[b].c - cm.learned[a].c) || (cm.learned[b].at - cm.learned[a].at));
187
- for (const k of keys.slice(INTEREST_CAP)) delete cm.learned[k];
193
+ const t = String(raw || '').replace(/```(?:json)?/gi, '');
194
+ const a = t.indexOf('{');
195
+ const b = t.lastIndexOf('}');
196
+ if (a < 0 || b <= a) return [];
197
+ const obj = JSON.parse(t.slice(a, b + 1));
198
+ const arr = Array.isArray(obj && obj.interests) ? obj.interests : [];
199
+ const seen = new Set();
200
+ const out = [];
201
+ for (const x of arr) {
202
+ const w = String(x || '').replace(/\s+/g, ' ').trim().slice(0, 40);
203
+ if (w.length < 3) continue;
204
+ if (/\d{4,}/.test(w)) continue; // telefon/tarih parçası etiket değil
205
+ const k = memFold(w);
206
+ if (!k || seen.has(k)) continue;
207
+ seen.add(k);
208
+ out.push(w);
209
+ if (out.length >= INTEREST_CAP) break;
188
210
  }
189
- saveChatMem(cm);
190
- return true;
211
+ return out;
191
212
  } catch {
192
- return false;
213
+ return [];
193
214
  }
194
215
  }
195
216
 
217
+ /* Çıkarılan etiketlerle öğrenilen listeyi TAMAMEN değiştir (günlük taze profil) */
218
+ function applyInferredInterests(llmText) {
219
+ const labels = parseInterestJson(llmText);
220
+ if (!labels.length) return { ok: false, labels: [] };
221
+ const cm = loadChatMem();
222
+ const now = Date.now();
223
+ cm.learned = {};
224
+ for (const w of labels) cm.learned[w] = { c: 1, at: now };
225
+ cm.learnedSource = 'llm';
226
+ cm.learnedAt = new Date(now).toISOString();
227
+ saveChatMem(cm);
228
+ return { ok: true, labels: [...labels], count: labels.length };
229
+ }
230
+
196
231
  /* Elle girilen + öğrenilen ilgi alanları birleşik (promptlar bunu kullanır) */
197
232
  function combinedInterests(cfg) {
198
233
  const manual = String((cfg && cfg.interests) || '').trim();
@@ -283,6 +318,7 @@ function normalizeRaw(item) {
283
318
  type,
284
319
  title,
285
320
  detail: String((item && item.detail) || '').slice(0, 300),
321
+ url: String((item && item.url) || '').slice(0, 600),
286
322
  sources: [(item && item.source) || type],
287
323
  scores: {},
288
324
  priority: 0,
@@ -313,7 +349,11 @@ async function fetchNews(topics) {
313
349
  const title = t ? String(t[1]).replace(/\s+/g, ' ').trim() : '';
314
350
  if (!title) continue;
315
351
  count++;
316
- out.push({ type: 'news', title, detail: 'Google News · ' + q, source: 'news:' + q });
352
+ /* haberin GERÇEK linki: kullanıcı "kaynak neymiş?" diye sorduğunda
353
+ Beast'in haberi bulabilmesi için bildirimle birlikte taşınır */
354
+ const l = /<link>(?:<!\[CDATA\[)?([\s\S]*?)(?:\]\]>)?<\/link>/.exec(m[0]);
355
+ const url = l ? String(l[1]).trim() : '';
356
+ out.push({ type: 'news', title, detail: 'Google News · ' + q, source: 'news:' + q, url });
317
357
  }
318
358
  } catch {}
319
359
  }
@@ -399,16 +439,21 @@ function parseFilterJson(text) {
399
439
  const COMPOSE_SYSTEM =
400
440
  'Beast adlı yerel yardımcı için proaktif bildirim metni yazarsın. Türkçe, samimi "kanka" tonunda, ' +
401
441
  'EN FAZLA 2 kısa cümle: ne olduğunu soyutla, neden önemli olabileceğini söyle, istersen tek kısa soru sor. ' +
402
- 'Alarm spam\'i yok; başlık/emoji/Markdown ekleme, yalnız düz metin yaz.';
442
+ 'Alarm spam\'i yok; başlık/emoji/Markdown ekleme, yalnız düz metin yaz. ' +
443
+ 'LINK URL\'sini KENDİN YAZMA — kaynak linki sistem tarafından mesajın sonuna OTOMATİK eklenir. ' +
444
+ 'Promptta AJAN DAVRANIŞI kuralı varsa ona UY; çelişirse davranış kuralı kazanır.';
403
445
 
404
446
  function composePrompt(ev, cfg) {
405
447
  const ctx = chatContextLine(5);
448
+ const behavior = String((cfg && cfg.behavior) || '').trim();
406
449
  return (
407
450
  'OLAY: ' + ev.title + '\n' +
408
451
  (ev.detail ? 'DETAY: ' + ev.detail + '\n' : '') +
409
452
  'KAYNAK: ' + ev.source + '\n' +
453
+ (ev.url ? 'LINK: ' + ev.url + '\n' : '') +
410
454
  'ÖNCELİK: ' + ev.priority + '/100 · ' + (ev.reason || '') + '\n' +
411
455
  'KULLANICI İLGİLERİ: ' + (combinedInterests(cfg) || '(belirtilmemiş)') + '\n' +
456
+ 'AJAN DAVRANIŞI (bu kurallara UY): ' + (behavior || '(özel kural yok — varsayılan samimi kanka tonu)') + '\n' +
412
457
  (ctx ? ctx + '\n' : '') +
413
458
  '\nBu olay için kullanıcıya gönderilecek proaktif kısa mesajı yaz.'
414
459
  );
@@ -580,6 +625,7 @@ function listEvents(limit) {
580
625
  type: e.type,
581
626
  title: e.title,
582
627
  detail: e.detail,
628
+ url: e.url || '',
583
629
  priority: e.priority,
584
630
  scores: e.scores || {},
585
631
  reason: e.reason,
@@ -602,7 +648,9 @@ module.exports = {
602
648
  listEvents,
603
649
  lastRunAt,
604
650
  rememberConversation,
605
- learnInterests,
651
+ interestPrompt,
652
+ applyInferredInterests,
653
+ parseInterestJson,
606
654
  combinedInterests,
607
655
  chatContextLine,
608
656
  memSnapshot,