beast-agent 0.26.5 → 0.27.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.
- package/package.json +1 -1
- package/src/agent/engine.js +36 -0
- package/src/main.js +97 -15
- package/src/renderer/i18n.js +2 -0
- package/src/renderer/renderer.js +26 -6
- package/tests/engine.test.js +76 -13
package/package.json
CHANGED
package/src/agent/engine.js
CHANGED
|
@@ -19,6 +19,9 @@ const SUB_MAX_TURNS = 8;
|
|
|
19
19
|
const HISTORY_TOKEN_BUDGET = 18000;
|
|
20
20
|
const TOOL_OUT_KEEP = 1200;
|
|
21
21
|
const USER_MAX = 8000;
|
|
22
|
+
/* observe() ile enjekte edilen bağlam mesajlarının öneki — ardışık bağlam
|
|
23
|
+
mesajlarının tek mesajda birleşmesi bu işaretle anlaşılır */
|
|
24
|
+
const OBSERVE_MARK = '[BAĞLAM';
|
|
22
25
|
|
|
23
26
|
/* Oturum notları (geçici hafıza): eski mesajlar bütçeden düşülmeden önce
|
|
24
27
|
önemli noktalar çıkarılır, sistem promptuna küçük blok olarak girer. */
|
|
@@ -1611,6 +1614,38 @@ class Engine {
|
|
|
1611
1614
|
return true;
|
|
1612
1615
|
}
|
|
1613
1616
|
|
|
1617
|
+
/* SESSİZ BAĞLAM ENJEKSİYONU: metin oturum geçmişine düşer ama _run()
|
|
1618
|
+
TETİKLENMEZ — ajan "görür", cevap ÜRETMEZ. WhatsApp gruplarında
|
|
1619
|
+
@mention moduyla kullanılır: grubun tüm konuşması bağlam olarak
|
|
1620
|
+
birikir, bot yalnız mention gelince konuşur. Ardışık bağlam
|
|
1621
|
+
mesajları işaret önekli tek user mesajında birleşir (bazı sağlayıcılar
|
|
1622
|
+
art arda user mesajını reddettiği için). */
|
|
1623
|
+
observe(sessionId, text) {
|
|
1624
|
+
const s = this._load(String(sessionId));
|
|
1625
|
+
if (!s) return false;
|
|
1626
|
+
const body = String(text || '').slice(0, USER_MAX);
|
|
1627
|
+
if (!body.trim()) return false;
|
|
1628
|
+
const lastMsg = s.messages[s.messages.length - 1];
|
|
1629
|
+
if (
|
|
1630
|
+
lastMsg && lastMsg.role === 'user' &&
|
|
1631
|
+
typeof lastMsg.content === 'string' &&
|
|
1632
|
+
lastMsg.content.startsWith(OBSERVE_MARK)
|
|
1633
|
+
) {
|
|
1634
|
+
/* önceki bağlam mesajının devamı — tek mesajda birleştir */
|
|
1635
|
+
lastMsg.content = (lastMsg.content + '\n' + body).slice(0, USER_MAX);
|
|
1636
|
+
this._rewriteLastMsg(s.id, lastMsg.content);
|
|
1637
|
+
} else {
|
|
1638
|
+
const msg = { role: 'user', content: body };
|
|
1639
|
+
s.messages.push(msg);
|
|
1640
|
+
try {
|
|
1641
|
+
this._append(s, msg);
|
|
1642
|
+
} catch {}
|
|
1643
|
+
}
|
|
1644
|
+
this.emit({ type: 'observe', sessionId: s.id });
|
|
1645
|
+
this.emit({ type: 'sessions' });
|
|
1646
|
+
return true;
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1614
1649
|
/* #14 paralel ajanlar: ANA oturumu hiç bloklamayan arka plan çalıştırıcı.
|
|
1615
1650
|
Ayrı gizli oturum açar, işi orada koşturur; bitince özet bildirim
|
|
1616
1651
|
istenen sohbete düşer. Birden fazla iş aynı anda paralel koşabilir —
|
|
@@ -3850,3 +3885,4 @@ module.exports.BG_HIDDEN_TOOLS = BG_HIDDEN_TOOLS;
|
|
|
3850
3885
|
module.exports.PERM_TOOL_SETS = PERM_TOOL_SETS;
|
|
3851
3886
|
module.exports.PERM_LEVELS = PERM_LEVELS;
|
|
3852
3887
|
module.exports.normalizePerms = normalizePerms;
|
|
3888
|
+
module.exports.OBSERVE_MARK = OBSERVE_MARK;
|
package/src/main.js
CHANGED
|
@@ -7,7 +7,7 @@ const http = require('http');
|
|
|
7
7
|
const dns = require('dns');
|
|
8
8
|
const crypto = require('crypto');
|
|
9
9
|
const { spawn } = require('child_process');
|
|
10
|
-
const Engine = require('./agent/engine');
|
|
10
|
+
const { Engine, OBSERVE_MARK } = require('./agent/engine');
|
|
11
11
|
const { loadBeastConfig, beastDir } = require('./agent/config');
|
|
12
12
|
const bots = require('./agent/bots');
|
|
13
13
|
const mqueue = require('./agent/mqueue');
|
|
@@ -1199,6 +1199,75 @@ async function waFlush(jid) {
|
|
|
1199
1199
|
}
|
|
1200
1200
|
}
|
|
1201
1201
|
|
|
1202
|
+
/* Grup göndereninin kimlik etiketi + SAHİP olup olmadığı.
|
|
1203
|
+
Sıra: izin listesindeki isim → @kullanıcı adı → gerçek PN → LID base.
|
|
1204
|
+
hitP.owner → bu kişi izin listesinde SAHİP olarak işaretli. */
|
|
1205
|
+
function waGroupSenderInfo(payload) {
|
|
1206
|
+
const pnDigits = String(payload.participantPn || '').split('@')[0].split(':')[0];
|
|
1207
|
+
const hitP = /^\d+$/.test(pnDigits) ? waFind(pnDigits) : null;
|
|
1208
|
+
const uname = String(payload.participantUsername || '').trim();
|
|
1209
|
+
let label;
|
|
1210
|
+
if (hitP && hitP.name) label = `${hitP.name} (+${pnDigits})`;
|
|
1211
|
+
else if (uname) label = `@${uname}${/^\d+$/.test(pnDigits) ? ' (+' + pnDigits + ')' : ''}`;
|
|
1212
|
+
else if (/^\d+$/.test(pnDigits)) label = '+' + pnDigits;
|
|
1213
|
+
else label = payload.participant ? '+' + String(payload.participant).split('@')[0].split(':')[0] : '';
|
|
1214
|
+
return { label, name: (hitP && hitP.name) || '', isOwner: !!(hitP && hitP.owner) };
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
/* Oturum yoksa oluştur (processWaMessage ile aynı kalıp) */
|
|
1218
|
+
function ensureWaSession(jid) {
|
|
1219
|
+
let sid = waChats.get(jid);
|
|
1220
|
+
if (!sid) {
|
|
1221
|
+
const v = engine.createSession();
|
|
1222
|
+
sid = v.id;
|
|
1223
|
+
waChats.set(jid, sid);
|
|
1224
|
+
saveWaChats();
|
|
1225
|
+
if (wa) wa.setWatchJids([...waChats.keys()]);
|
|
1226
|
+
} else {
|
|
1227
|
+
waRememberSession(jid, sid);
|
|
1228
|
+
}
|
|
1229
|
+
return sid;
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
/* ---------- GRUP BAĞLAM AKIŞI (mentionOnly + seeAll) ----------
|
|
1233
|
+
Bot grubun tüm konuşmasını okur ama CEVAP ÜRETMEZ; mesajlar sessizce
|
|
1234
|
+
oturum geçmişine bağlam olarak düşer. @mention gelince bot tüm bu
|
|
1235
|
+
bağlamı görerek konuşur. Anti-spam: aynı birleştirme penceresi. */
|
|
1236
|
+
const waCtxQueue = new Map(); // jid -> { timer, payloads[] }
|
|
1237
|
+
|
|
1238
|
+
function waGroupObserve(jid, payload, senderNum) {
|
|
1239
|
+
let q = waCtxQueue.get(jid);
|
|
1240
|
+
if (!q) {
|
|
1241
|
+
q = { payloads: [] };
|
|
1242
|
+
waCtxQueue.set(jid, q);
|
|
1243
|
+
}
|
|
1244
|
+
q.payloads.push({ payload, senderNum });
|
|
1245
|
+
clearTimeout(q.timer);
|
|
1246
|
+
waLog(`grup bağlam: kuyruğa girdi jid=${waPrettyJid(jid)} toplam=${q.payloads.length}`);
|
|
1247
|
+
q.timer = setTimeout(() => {
|
|
1248
|
+
waCtxFlush(jid).catch((e) => waLog(`ctx flush KRASİ: ${String((e && e.stack) || e)}`));
|
|
1249
|
+
}, WA_DEBOUNCE_MS);
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
async function waCtxFlush(jid) {
|
|
1253
|
+
const q = waCtxQueue.get(jid);
|
|
1254
|
+
if (!q) return;
|
|
1255
|
+
waCtxQueue.delete(jid);
|
|
1256
|
+
const sid = ensureWaSession(jid);
|
|
1257
|
+
/* her satır gönderen etiketli olur — ajan kimin ne yazdığını izleyebilsin */
|
|
1258
|
+
const lines = [];
|
|
1259
|
+
for (const { payload } of q.payloads) {
|
|
1260
|
+
if (!payload || !payload.text) continue;
|
|
1261
|
+
const gi = waGroupSenderInfo(payload);
|
|
1262
|
+
const who = gi.label || '?';
|
|
1263
|
+
lines.push(`${who}: ${String(payload.text).slice(0, 1500)}`);
|
|
1264
|
+
}
|
|
1265
|
+
if (!lines.length) return;
|
|
1266
|
+
const text = `${OBSERVE_MARK} — WhatsApp grup konuşması (cevap verme, sadece bilgi olarak sakla)]\n` + lines.join('\n').slice(0, 6000);
|
|
1267
|
+
engine.observe(sid, text);
|
|
1268
|
+
waLog(`grup bağlam: oturuma işlendi sid=${sid} satır=${lines.length}`);
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1202
1271
|
/* ---------- WA'dan skill kurulumu ----------
|
|
1203
1272
|
SKILL.md (veya *.skill.md) belgesi atılırsa skills altına kurulur. */
|
|
1204
1273
|
|
|
@@ -1549,7 +1618,15 @@ async function handleWaIncoming(jid, payload, senderNum) {
|
|
|
1549
1618
|
if (isGroup) {
|
|
1550
1619
|
const g = settings.waGroups || {};
|
|
1551
1620
|
if (!g.enabled) return;
|
|
1552
|
-
|
|
1621
|
+
const mentionMode = g.mentionOnly !== false;
|
|
1622
|
+
/* MENTION MODU + seeAll: bot grubun TÜM konuşmasını BAĞLAM olarak görür
|
|
1623
|
+
ama yalnız @mention'da cevap üretir. seeAll VARSAYILAN KAPALI —
|
|
1624
|
+
mention'sız mesajlar tamamen yutulur (gizlilik). */
|
|
1625
|
+
if (mentionMode && !payload.mentioned && g.seeAll) {
|
|
1626
|
+
waGroupObserve(jid, payload, senderNum);
|
|
1627
|
+
return;
|
|
1628
|
+
}
|
|
1629
|
+
if (mentionMode && !payload.mentioned) return;
|
|
1553
1630
|
waLog(`grup mesajı jid=${waPrettyJid(jid)} participant=+${senderNum || '?'} mention=${!!payload.mentioned}`);
|
|
1554
1631
|
} else {
|
|
1555
1632
|
const hit = waFind(senderNum);
|
|
@@ -1725,22 +1802,21 @@ async function processWaMessage(jid, payload, senderNum, requeues = 0) {
|
|
|
1725
1802
|
Kimlik çözümleme sırası: izin listesindeki isim → @kullanıcı adı → gerçek PN → LID.
|
|
1726
1803
|
Ajan böylece grubun içinde mesajın KİMDEN geldiğini görür. */
|
|
1727
1804
|
let groupSender = '';
|
|
1805
|
+
let groupSenderOwner = false;
|
|
1728
1806
|
if (isGroup) {
|
|
1729
|
-
const
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
if (hitP && hitP.name) groupSender = `${hitP.name} (+${pnDigits})`;
|
|
1733
|
-
else if (uname) groupSender = `@${uname}${/^\d+$/.test(pnDigits) ? ' (+' + pnDigits + ')' : ''}`;
|
|
1734
|
-
else if (/^\d+$/.test(pnDigits)) groupSender = '+' + pnDigits;
|
|
1735
|
-
else groupSender = participantName || '';
|
|
1807
|
+
const gi = waGroupSenderInfo(payload);
|
|
1808
|
+
groupSender = gi.label || participantName;
|
|
1809
|
+
groupSenderOwner = gi.isOwner;
|
|
1736
1810
|
}
|
|
1737
|
-
/* #v13.1 rol: SAHİP vs MİSAFİR — ajan kime konuştuğunu net bilsin
|
|
1811
|
+
/* #v13.1 rol: SAHİP vs MİSAFİR — ajan kime konuştuğunu net bilsin.
|
|
1812
|
+
GRUPLARDA DA: gönderen izin listesinde SAHİP olarak işaretliyse ajan
|
|
1813
|
+
bunu görür — sahibinin grup içi talepleri misafir sözünden önceliklidir. */
|
|
1738
1814
|
const isOwner = !isGroup && !!hit.owner;
|
|
1739
|
-
const roleTag = isOwner
|
|
1815
|
+
const roleTag = isOwner || groupSenderOwner
|
|
1740
1816
|
? 'SAHİBİN (talepleri önceliklidir)'
|
|
1741
1817
|
: 'MİSAFİR (izinli ama sahibin sözü önceliklidir)';
|
|
1742
1818
|
const label = isGroup
|
|
1743
|
-
? `Grup ${jid.split('@')[0]}${groupSender ?
|
|
1819
|
+
? `Grup ${jid.split('@')[0]}${groupSender ? ` — gönderen: ${groupSender} — ${groupSenderOwner ? 'SAHİBİN' : 'MİSAFİR (grup üyesi)'}` : ''}`
|
|
1744
1820
|
: hit.name
|
|
1745
1821
|
? `${hit.name} (+${senderNum || '?'}) — ${roleTag}`
|
|
1746
1822
|
: `+${senderNum || '?'} — ${roleTag}`;
|
|
@@ -1748,7 +1824,9 @@ async function processWaMessage(jid, payload, senderNum, requeues = 0) {
|
|
|
1748
1824
|
if (!isGroup && !isOwner) {
|
|
1749
1825
|
text += `\n[NOT: Bu kişi SAHİP DEĞİL, misafirdir. Sahibin ayarlarını/verilerini değiştirme; kalıcı hafızaya misafire özel bilgi yazma.]`;
|
|
1750
1826
|
} else if (isGroup) {
|
|
1751
|
-
text +=
|
|
1827
|
+
text += groupSenderOwner
|
|
1828
|
+
? `\n[NOT: Grup mesajı ama gönderen SAHİBİN — talepleri önceliklidir, misafir gibi temkinli konuşmana gerek yok.]`
|
|
1829
|
+
: `\n[NOT: Grup mesajı — gönderen grubun üyesidir, izin listendeki kişi olmayabilir. Grup üyelerine karşı temkinli konuş.]`;
|
|
1752
1830
|
}
|
|
1753
1831
|
const attachments = [];
|
|
1754
1832
|
|
|
@@ -4551,12 +4629,16 @@ ipcMain.handle('wherewasi:set', (_e, cfg) => {
|
|
|
4551
4629
|
return settings.whereWasI;
|
|
4552
4630
|
});
|
|
4553
4631
|
|
|
4554
|
-
/* WhatsApp grup ayarı: { enabled, mentionOnly }
|
|
4555
|
-
|
|
4632
|
+
/* WhatsApp grup ayarı: { enabled, mentionOnly, seeAll }
|
|
4633
|
+
seeAll: yalnız mentionOnly modunda anlamlı — bot grubun TÜM konuşmasını
|
|
4634
|
+
bağlam olarak okur ama yine SADECE @mention'da cevap verir.
|
|
4635
|
+
VARSAYILAN KAPALI: herkesin konuşmasını görmesi istenmeyebilir. */
|
|
4636
|
+
ipcMain.handle('wa:groups:get', () => settings.waGroups || { enabled: false, mentionOnly: true, seeAll: false });
|
|
4556
4637
|
ipcMain.handle('wa:groups:set', (_e, cfg) => {
|
|
4557
4638
|
settings.waGroups = {
|
|
4558
4639
|
enabled: !!(cfg && cfg.enabled),
|
|
4559
4640
|
mentionOnly: !(cfg && cfg.mentionOnly === false),
|
|
4641
|
+
seeAll: !!(cfg && cfg.seeAll),
|
|
4560
4642
|
};
|
|
4561
4643
|
saveSettings();
|
|
4562
4644
|
return settings.waGroups;
|
package/src/renderer/i18n.js
CHANGED
|
@@ -197,6 +197,7 @@
|
|
|
197
197
|
it_reset_pair: 'Eşlemeyi Sıfırla',
|
|
198
198
|
it_groups_on: 'WhatsApp gruplarında çalış',
|
|
199
199
|
it_groups_mention: 'Sadece @mention edilince cevap ver (grupta botu etiketle)',
|
|
200
|
+
it_groups_seeall: 'Mention modunda grubun TÜM konuşmalarını oku (bağlam biriktirir, yine sadece @mention\'a cevap verir)',
|
|
200
201
|
it_groups_all: 'Grubun her mesajına karış',
|
|
201
202
|
it_allow_label: 'İzinli numaralar',
|
|
202
203
|
it_name_ph: 'İsim (zorunlu)',
|
|
@@ -726,6 +727,7 @@
|
|
|
726
727
|
it_reset_pair: 'Reset pairing',
|
|
727
728
|
it_groups_on: 'Work in WhatsApp groups',
|
|
728
729
|
it_groups_mention: 'Reply only when @mentioned (tag the bot in the group)',
|
|
730
|
+
it_groups_seeall: 'In mention mode, read ALL group messages as context (still replies only when @mentioned)',
|
|
729
731
|
it_groups_all: 'Respond to every group message',
|
|
730
732
|
it_allow_label: 'Allowed numbers',
|
|
731
733
|
it_name_ph: 'Name (required)',
|
package/src/renderer/renderer.js
CHANGED
|
@@ -1473,7 +1473,7 @@ const WA_STATUS_TEXT = {
|
|
|
1473
1473
|
|
|
1474
1474
|
async function renderIntegrationsPane() {
|
|
1475
1475
|
const pane = $('#tab-integrations');
|
|
1476
|
-
const g = await beast.waGetGroups().catch(() => ({ enabled: false, mentionOnly: true }));
|
|
1476
|
+
const g = await beast.waGetGroups().catch(() => ({ enabled: false, mentionOnly: true, seeAll: false }));
|
|
1477
1477
|
pane.innerHTML =
|
|
1478
1478
|
'<h2>' + _t('it_h2') + '</h2><div class="sub">' + _t('it_sub') + '</div>' +
|
|
1479
1479
|
`<div class="wa-card">
|
|
@@ -1500,6 +1500,7 @@ async function renderIntegrationsPane() {
|
|
|
1500
1500
|
<label class="lock-row"><input type="checkbox" id="waGroupsOn" ${g.enabled ? 'checked' : ''}/><span>${_t('it_groups_on')}</span></label>
|
|
1501
1501
|
<label class="lock-row" style="${g.enabled ? '' : 'opacity:.45'}"><input type="radio" name="waGroupMode" id="waGroupsMention" ${g.mentionOnly !== false ? 'checked' : ''}/><span>${_t('it_groups_mention')}</span></label>
|
|
1502
1502
|
<label class="lock-row" style="${g.enabled ? '' : 'opacity:.45'}"><input type="radio" name="waGroupMode" id="waGroupsAll" ${g.mentionOnly === false ? 'checked' : ''}/><span>${_t('it_groups_all')}</span></label>
|
|
1503
|
+
<label class="lock-row" id="waSeeAllRow" style="${g.enabled && g.mentionOnly !== false ? '' : 'opacity:.45'}"><input type="checkbox" id="waGroupsSeeAll" ${g.seeAll ? 'checked' : ''}/><span>${_t('it_groups_seeall')}</span></label>
|
|
1503
1504
|
<div class="divider"></div>
|
|
1504
1505
|
<label class="mem-label" style="margin-top:0">${_t('it_allow_label')}</label>
|
|
1505
1506
|
<div id="waAllowChips" class="chips-inline"></div>
|
|
@@ -1574,22 +1575,41 @@ async function renderIntegrationsPane() {
|
|
|
1574
1575
|
const waGroupsOn = $('#waGroupsOn');
|
|
1575
1576
|
const waGroupsMention = $('#waGroupsMention');
|
|
1576
1577
|
const waGroupsAll = $('#waGroupsAll');
|
|
1577
|
-
|
|
1578
|
+
const waGroupsSeeAll = $('#waGroupsSeeAll');
|
|
1579
|
+
const waSeeAllRow = $('#waSeeAllRow');
|
|
1580
|
+
/* iki mod BİRBİRİNE HARIÇTİR: ya @mention ya her mesaja karışma (radio)
|
|
1581
|
+
seeAll yalnız mention modunda anlamlı — o mod seçiliyken aktifleşir */
|
|
1578
1582
|
const syncGroupMode = () => {
|
|
1579
1583
|
const op = waGroupsOn.checked ? '' : '.45';
|
|
1580
1584
|
if (waGroupsMention) waGroupsMention.parentElement.style.opacity = op;
|
|
1581
1585
|
if (waGroupsAll) waGroupsAll.parentElement.style.opacity = op;
|
|
1586
|
+
if (waSeeAllRow) waSeeAllRow.style.opacity = waGroupsOn.checked && waGroupsMention && waGroupsMention.checked ? '' : '.45';
|
|
1582
1587
|
};
|
|
1583
1588
|
const saveGroups = async () => {
|
|
1584
|
-
const r = await beast.waSetGroups({ enabled: waGroupsOn.checked, mentionOnly: !waGroupsAll.checked });
|
|
1585
|
-
toast(
|
|
1589
|
+
const r = await beast.waSetGroups({ enabled: waGroupsOn.checked, mentionOnly: !waGroupsAll.checked, seeAll: !!(waGroupsSeeAll && waGroupsSeeAll.checked) });
|
|
1590
|
+
toast(
|
|
1591
|
+
r.enabled
|
|
1592
|
+
? r.mentionOnly
|
|
1593
|
+
? r.seeAll
|
|
1594
|
+
? 'Gruplar açık — tüm konuşmalar bağlam olarak okunuyor, sadece @mention\'a cevap verir'
|
|
1595
|
+
: 'Gruplar açık — @mention bekler'
|
|
1596
|
+
: 'Gruplar açık — tüm mesajlara karışır'
|
|
1597
|
+
: 'Gruplar kapalı'
|
|
1598
|
+
);
|
|
1586
1599
|
};
|
|
1587
1600
|
waGroupsOn.addEventListener('change', async () => {
|
|
1588
1601
|
syncGroupMode();
|
|
1589
1602
|
await saveGroups();
|
|
1590
1603
|
});
|
|
1591
|
-
waGroupsMention.addEventListener('change',
|
|
1592
|
-
|
|
1604
|
+
waGroupsMention.addEventListener('change', () => {
|
|
1605
|
+
syncGroupMode();
|
|
1606
|
+
saveGroups();
|
|
1607
|
+
});
|
|
1608
|
+
waGroupsAll.addEventListener('change', () => {
|
|
1609
|
+
syncGroupMode();
|
|
1610
|
+
saveGroups();
|
|
1611
|
+
});
|
|
1612
|
+
if (waGroupsSeeAll) waGroupsSeeAll.addEventListener('change', saveGroups);
|
|
1593
1613
|
syncGroupMode();
|
|
1594
1614
|
|
|
1595
1615
|
$('#waStartBtn').addEventListener('click', async () => {
|
package/tests/engine.test.js
CHANGED
|
@@ -113,28 +113,91 @@ test('eski görseller metne indirilir, son mesajın görseli korunur', () => {
|
|
|
113
113
|
assert.ok(Array.isArray(lastUser.content));
|
|
114
114
|
});
|
|
115
115
|
|
|
116
|
-
/* ---------- oturum notlar� (ge�ici haf�za) ---------- */
|
|
116
|
+
/* ---------- oturum notlar� (ge�ici haf�za) ---------- */
|
|
117
117
|
|
|
118
|
-
test('oturum notlar�: dosyadan y�klenir, mesaj listesine kar��maz', () => {
|
|
118
|
+
test('oturum notlar�: dosyadan y�klenir, mesaj listesine kar��maz', () => {
|
|
119
119
|
const eng = makeEngine();
|
|
120
120
|
const id = eng.createSession().id;
|
|
121
121
|
for (let i = 0; i < 5; i++) {
|
|
122
122
|
fs.appendFileSync(eng._file(id), JSON.stringify({ t: 'msg', role: 'user', content: 'mesaj ' + i }) + '\n');
|
|
123
123
|
}
|
|
124
|
-
fs.appendFileSync(eng._file(id), JSON.stringify({ t: 'notes', text: 'hedef: v7 �al�smas�', at: 3 }) + '\n');
|
|
125
|
-
eng.cache.delete(id); // diskten taze y�kle
|
|
124
|
+
fs.appendFileSync(eng._file(id), JSON.stringify({ t: 'notes', text: 'hedef: v7 �al�smas�', at: 3 }) + '\n');
|
|
125
|
+
eng.cache.delete(id); // diskten taze y�kle
|
|
126
126
|
const s = eng._load(id);
|
|
127
|
-
assert.equal(s.notes, 'hedef: v7 �al�smas�');
|
|
127
|
+
assert.equal(s.notes, 'hedef: v7 �al�smas�');
|
|
128
128
|
assert.equal(s.notesAt, 3);
|
|
129
129
|
assert.equal(s.messages.length, 5);
|
|
130
130
|
});
|
|
131
131
|
|
|
132
|
-
test('oturum notlar� varken payload s�k�la��r', () => {
|
|
133
|
-
const eng = makeEngine({ historyTokenBudget: 100000 });
|
|
134
|
-
const msgs = [];
|
|
135
|
-
for (let i = 0; i < 30; i++) msgs.push({ role: 'user', content: 'kisa mesaj #' + i });
|
|
136
|
-
const withoutNotes = eng._buildPayload('sys', msgs, null);
|
|
137
|
-
const withNotes = eng._buildPayload('sys', msgs, '�zet: test');
|
|
138
|
-
assert.ok(withNotes.length < withoutNotes.length);
|
|
139
|
-
assert.equal(withNotes[withNotes.length - 1].content, 'kisa mesaj #29'); // son mesaj hep kal�r
|
|
132
|
+
test('oturum notlar� varken payload s�k�la��r', () => {
|
|
133
|
+
const eng = makeEngine({ historyTokenBudget: 100000 });
|
|
134
|
+
const msgs = [];
|
|
135
|
+
for (let i = 0; i < 30; i++) msgs.push({ role: 'user', content: 'kisa mesaj #' + i });
|
|
136
|
+
const withoutNotes = eng._buildPayload('sys', msgs, null);
|
|
137
|
+
const withNotes = eng._buildPayload('sys', msgs, '�zet: test');
|
|
138
|
+
assert.ok(withNotes.length < withoutNotes.length);
|
|
139
|
+
assert.equal(withNotes[withNotes.length - 1].content, 'kisa mesaj #29'); // son mesaj hep kal�r
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
/* ---------- observe(): sessiz bağlam enjeksiyonu ---------- */
|
|
143
|
+
|
|
144
|
+
test('observe: mesaj geçmişe düşer ama run tetiklenmez', async () => {
|
|
145
|
+
const events = [];
|
|
146
|
+
const eng = makeEngine({ emit: (ev) => events.push(ev) });
|
|
147
|
+
const id = eng.createSession().id;
|
|
148
|
+
const ok = eng.observe(id, '[BAĞLAM — grup] ali: selam');
|
|
149
|
+
assert.ok(ok);
|
|
150
|
+
const s = eng._load(id);
|
|
151
|
+
assert.equal(s.messages.length, 1);
|
|
152
|
+
assert.equal(s.messages[0].role, 'user');
|
|
153
|
+
assert.ok(s.messages[0].content.startsWith('[BAĞLAM'));
|
|
154
|
+
// _run tetiklenmedi: LLM hatası/done olayı yok
|
|
155
|
+
await new Promise((r) => setTimeout(r, 60));
|
|
156
|
+
assert.ok(!events.some((e) => e.type === 'error' || e.type === 'done'));
|
|
157
|
+
// observe olayı yayıldı
|
|
158
|
+
assert.ok(events.some((e) => e.type === 'observe' && e.sessionId === id));
|
|
159
|
+
// boş metin false
|
|
160
|
+
assert.equal(eng.observe(id, ' '), false);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test('observe: ardışık bağlam mesajları tek mesajda birleşir', () => {
|
|
164
|
+
const eng = makeEngine();
|
|
165
|
+
const id = eng.createSession().id;
|
|
166
|
+
eng.observe(id, '[BAĞLAM — grup] ali: selam');
|
|
167
|
+
eng.observe(id, '[BAĞLAM — grup] ayşe: nasılsın');
|
|
168
|
+
const s = eng._load(id);
|
|
169
|
+
assert.equal(s.messages.length, 1);
|
|
170
|
+
assert.ok(s.messages[0].content.includes('ali: selam'));
|
|
171
|
+
assert.ok(s.messages[0].content.includes('ayşe: nasılsın'));
|
|
172
|
+
// dosyaya da tek satır yazıldı
|
|
173
|
+
const lines = fs.readFileSync(eng._file(id), 'utf8').split('\n').filter((l) => l.trim());
|
|
174
|
+
const msgLines = lines.filter((l) => { try { return JSON.parse(l).t === 'msg'; } catch { return false; } });
|
|
175
|
+
assert.equal(msgLines.length, 1);
|
|
140
176
|
});
|
|
177
|
+
|
|
178
|
+
test('observe: normal user mesajıyla birleşmez', () => {
|
|
179
|
+
const eng = makeEngine();
|
|
180
|
+
const id = eng.createSession().id;
|
|
181
|
+
const s = eng._load(id);
|
|
182
|
+
s.messages.push({ role: 'user', content: 'gerçek soru' });
|
|
183
|
+
fs.appendFileSync(eng._file(id), JSON.stringify({ t: 'msg', role: 'user', content: 'gerçek soru' }) + '\n');
|
|
184
|
+
eng.observe(id, '[BAĞLAM — grup] ali: selam');
|
|
185
|
+
assert.equal(s.messages.length, 2);
|
|
186
|
+
assert.equal(s.messages[0].content, 'gerçek soru');
|
|
187
|
+
assert.ok(s.messages[1].content.startsWith('[BAĞLAM'));
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test('send: mention mesajı bekleyen bağlam mesajıyla birleşir ve run başlar', async () => {
|
|
191
|
+
const events = [];
|
|
192
|
+
const eng = makeEngine({ emit: (ev) => events.push(ev) });
|
|
193
|
+
const id = eng.createSession().id;
|
|
194
|
+
eng.observe(id, '[BAĞLAM — grup] ali: selam\nayşe: nasılsın');
|
|
195
|
+
eng.send(id, { text: '@beast bana bugün hava nasıl?' });
|
|
196
|
+
const s = eng._load(id);
|
|
197
|
+
assert.equal(s.messages.length, 1);
|
|
198
|
+
assert.ok(s.messages[0].content.includes('ayşe: nasılsın'));
|
|
199
|
+
assert.ok(s.messages[0].content.includes('@beast bana bugün hava nasıl?'));
|
|
200
|
+
// run başladı → sağlayıcı yoksa hata olayı düşer (tetiklenme kanıtı)
|
|
201
|
+
await new Promise((r) => setTimeout(r, 120));
|
|
202
|
+
assert.ok(events.some((e) => e.type === 'error' || e.type === 'done'));
|
|
203
|
+
});
|