beast-agent 2.4.1 → 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 +1 -1
- package/src/agent/engine.js +87 -5
- package/src/agent/nightref.js +35 -0
- package/src/agent/perception.js +95 -47
- package/src/main.js +231 -40
- package/src/preload.js +4 -0
- package/src/renderer/i18n.js +18 -10
- package/src/renderer/index.html +8 -12
- package/src/renderer/renderer.js +199 -130
- package/src/renderer/style.css +98 -51
package/package.json
CHANGED
package/src/agent/engine.js
CHANGED
|
@@ -987,9 +987,11 @@ class Engine {
|
|
|
987
987
|
if (session.code) this._codeIndex.set(session.code, session.id);
|
|
988
988
|
}
|
|
989
989
|
|
|
990
|
-
/* Oturum dosyasındaki son 'msg' satırını
|
|
991
|
-
yanıtsız kalmış user mesajıyla yeni mesaj birleştirildiğinde
|
|
992
|
-
|
|
990
|
+
/* Oturum dosyasındaki son 'msg' satırını güncel içerikle değiştir —
|
|
991
|
+
yanıtsız kalmış user mesajıyla yeni mesaj birleştirildiğinde (role:'user')
|
|
992
|
+
ya da proaktif not son asistan mesajına birleştirildiğinde (role:'assistant') */
|
|
993
|
+
_rewriteLastMsg(id, content, attachments, role) {
|
|
994
|
+
const wantRole = role === 'assistant' ? 'assistant' : 'user';
|
|
993
995
|
try {
|
|
994
996
|
const file = this._file(String(id));
|
|
995
997
|
const lines = fs.readFileSync(file, 'utf8').split('\n');
|
|
@@ -997,8 +999,8 @@ class Engine {
|
|
|
997
999
|
if (!lines[i].trim()) continue;
|
|
998
1000
|
let r;
|
|
999
1001
|
try { r = JSON.parse(lines[i]); } catch { continue; }
|
|
1000
|
-
if (r.t === 'msg' && r.role ===
|
|
1001
|
-
const out = { t: 'msg', role:
|
|
1002
|
+
if (r.t === 'msg' && r.role === wantRole) {
|
|
1003
|
+
const out = { t: 'msg', role: wantRole, content: String(content || '') };
|
|
1002
1004
|
if (Array.isArray(attachments) && attachments.length) out.attachments = attachments;
|
|
1003
1005
|
lines[i] = JSON.stringify(out);
|
|
1004
1006
|
break;
|
|
@@ -1092,6 +1094,9 @@ class Engine {
|
|
|
1092
1094
|
session.isBotDm = true;
|
|
1093
1095
|
session.dmA = String(rec.a || '');
|
|
1094
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 || '');
|
|
1095
1100
|
} else if (rec.t === 'msg') {
|
|
1096
1101
|
delete rec.t;
|
|
1097
1102
|
session.messages.push(rec);
|
|
@@ -1251,6 +1256,42 @@ class Engine {
|
|
|
1251
1256
|
return out.slice(0, 100);
|
|
1252
1257
|
}
|
|
1253
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
|
+
|
|
1254
1295
|
createSession() {
|
|
1255
1296
|
const id = uid();
|
|
1256
1297
|
const code = this._newCode();
|
|
@@ -2284,6 +2325,40 @@ class Engine {
|
|
|
2284
2325
|
return true;
|
|
2285
2326
|
}
|
|
2286
2327
|
|
|
2328
|
+
/* PROAKTİF MESAJ ENJEKSİYONU (Empati Loop): ajanın az önce KULLANICIYA
|
|
2329
|
+
bildirdiği metni, yeni bir tur BAŞLATMADAN geçmişe kendi asistan
|
|
2330
|
+
mesajı olarak işler. Model bir sonraki turda bu mesajı bağlamında
|
|
2331
|
+
"benim söylediğim söz" olarak görür — kullanıcı cevap verdiğinde
|
|
2332
|
+
sohbet aynı oturumda kopmadan devam eder. Son mesaj araçsız bir
|
|
2333
|
+
asistan mesajıysa metin ona BİRLEŞTİRİLİR — katı sağlayıcılarda
|
|
2334
|
+
art arda asistan mesajı gitmez. */
|
|
2335
|
+
injectAssistant(sessionId, text) {
|
|
2336
|
+
const s = this._load(String(sessionId));
|
|
2337
|
+
if (!s) return false;
|
|
2338
|
+
const body = String(text || '').slice(0, USER_MAX);
|
|
2339
|
+
if (!body.trim()) return false;
|
|
2340
|
+
const lastMsg = s.messages[s.messages.length - 1];
|
|
2341
|
+
if (
|
|
2342
|
+
lastMsg &&
|
|
2343
|
+
lastMsg.role === 'assistant' &&
|
|
2344
|
+
typeof lastMsg.content === 'string' &&
|
|
2345
|
+
!(Array.isArray(lastMsg.tool_calls) && lastMsg.tool_calls.length)
|
|
2346
|
+
) {
|
|
2347
|
+
lastMsg.content = (lastMsg.content + '\n\n' + body).slice(0, USER_MAX);
|
|
2348
|
+
this._rewriteLastMsg(s.id, lastMsg.content, null, 'assistant');
|
|
2349
|
+
this.emit({ type: 'sessions' });
|
|
2350
|
+
return true;
|
|
2351
|
+
}
|
|
2352
|
+
const msg = { role: 'assistant', content: body };
|
|
2353
|
+
s.messages.push(msg);
|
|
2354
|
+
try {
|
|
2355
|
+
this._append(s, msg);
|
|
2356
|
+
} catch {}
|
|
2357
|
+
this.emit({ type: 'message', sessionId: s.id, message: msg });
|
|
2358
|
+
this.emit({ type: 'sessions' });
|
|
2359
|
+
return true;
|
|
2360
|
+
}
|
|
2361
|
+
|
|
2287
2362
|
/* Paralel ajan GEÇMİŞİNİ topluca sil: çalışanlar/ bekleyenler iptal edilir,
|
|
2288
2363
|
tüm bg oturum dosyaları + kalıcı kayıt temizlenir. Döndürür: silinen iş sayısı */
|
|
2289
2364
|
clearAllBgJobs() {
|
|
@@ -3668,6 +3743,9 @@ const skills = require('./skills');
|
|
|
3668
3743
|
clearTimeout(kill);
|
|
3669
3744
|
}
|
|
3670
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');
|
|
3671
3749
|
return await nightref.run({
|
|
3672
3750
|
llm,
|
|
3673
3751
|
memory,
|
|
@@ -3676,6 +3754,10 @@ const skills = require('./skills');
|
|
|
3676
3754
|
sinceIso,
|
|
3677
3755
|
manual,
|
|
3678
3756
|
now: new Date(),
|
|
3757
|
+
interests: {
|
|
3758
|
+
prompt: () => perception.interestPrompt(),
|
|
3759
|
+
apply: (raw) => perception.applyInferredInterests(raw),
|
|
3760
|
+
},
|
|
3679
3761
|
log: (msg) => log.info('gece-yansima', msg),
|
|
3680
3762
|
});
|
|
3681
3763
|
} finally {
|
package/src/agent/nightref.js
CHANGED
|
@@ -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' +
|
package/src/agent/perception.js
CHANGED
|
@@ -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
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
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
|
-
/*
|
|
169
|
-
|
|
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
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
if (
|
|
175
|
-
const
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
|
|
190
|
-
return true;
|
|
211
|
+
return out;
|
|
191
212
|
} catch {
|
|
192
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
651
|
+
interestPrompt,
|
|
652
|
+
applyInferredInterests,
|
|
653
|
+
parseInterestJson,
|
|
606
654
|
combinedInterests,
|
|
607
655
|
chatContextLine,
|
|
608
656
|
memSnapshot,
|