beast-agent 0.26.4 → 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/agent/whatsapp.js +36 -6
- package/src/main.js +97 -15
- package/src/renderer/i18n.js +8 -4
- package/src/renderer/renderer.js +36 -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/agent/whatsapp.js
CHANGED
|
@@ -337,8 +337,20 @@ class WhatsAppBridge {
|
|
|
337
337
|
async _processIncoming(msg) {
|
|
338
338
|
try {
|
|
339
339
|
const jid = msg.key && msg.key.remoteJid;
|
|
340
|
-
if (!jid
|
|
340
|
+
if (!jid) return;
|
|
341
341
|
if (jid === 'status@broadcast' || jid.endsWith('@newsletter')) return;
|
|
342
|
+
/* botun kendi kimliği: telefon base'i + LID base'i (LID dönemi) */
|
|
343
|
+
const meBase = String(this._userIdRaw || '').split('@')[0].split(':')[0];
|
|
344
|
+
const lidBase = String((this.sock && this.sock.user && this.sock.user.lid) || '').split('@')[0].split(':')[0];
|
|
345
|
+
/* KENDİ KENDİNE SOHBET (Saved Messages): kullanıcı kendi numarasını izinliye
|
|
346
|
+
ekleyip kendi sohbetinden bota yazabilsin. Bu sohbetteki HER mesaj fromMe
|
|
347
|
+
gelir; botun KENDİ gönderdiği mesajlar (_tracked) filtrelenir → döngü yok.
|
|
348
|
+
Diğer sohbetlerdeki elle yazılan fromMe mesajları bot etkilenmesin diye yutulur. */
|
|
349
|
+
const selfChat = !!(meBase && (jid === meBase + '@s.whatsapp.net' || (lidBase && jid === lidBase + '@lid')));
|
|
350
|
+
if (msg.key.fromMe) {
|
|
351
|
+
if (this._tracked.has(String(msg.key.id))) return; // botun kendi cevabı — echo
|
|
352
|
+
if (!selfChat) return;
|
|
353
|
+
}
|
|
342
354
|
const isGroup = jid.endsWith('@g.us');
|
|
343
355
|
const participantJid = isGroup && msg.key.participant ? String(msg.key.participant) : '';
|
|
344
356
|
const mm = msg.message || {};
|
|
@@ -351,20 +363,35 @@ class WhatsAppBridge {
|
|
|
351
363
|
const t = String(text || '').trim();
|
|
352
364
|
|
|
353
365
|
// gruplarda bot'un kendisi @mention edilmiş mi
|
|
366
|
+
/* LİD DÖNEMİ UYARISI: mentionedJid artık çoğu zaman botun @lid numarasıyla
|
|
367
|
+
gelir (telefon JID'i DEĞİL) — bu yüzden yalnız meBase ile karşılaştırma
|
|
368
|
+
mention'ı ıskalıyordu. Şimdi: botun telefon base'i + LID base'i ikisiyle
|
|
369
|
+
de eşitlik aranır; contextInfo görsel/video/belge caption'larından da alınır. */
|
|
354
370
|
let mentioned = false;
|
|
355
371
|
if (isGroup) {
|
|
356
|
-
const ci =
|
|
372
|
+
const ci =
|
|
373
|
+
(mm.extendedTextMessage && mm.extendedTextMessage.contextInfo) ||
|
|
374
|
+
(mm.imageMessage && mm.imageMessage.contextInfo) ||
|
|
375
|
+
(mm.videoMessage && mm.videoMessage.contextInfo) ||
|
|
376
|
+
(mm.documentMessage && mm.documentMessage.contextInfo) ||
|
|
377
|
+
null;
|
|
357
378
|
const men = (ci && ci.mentionedJid) || [];
|
|
358
|
-
const
|
|
379
|
+
const baseOf = (j) => String(j || '').split('?')[0].split(':')[0].split('@')[0];
|
|
359
380
|
for (const m of men) {
|
|
360
|
-
|
|
381
|
+
const base = baseOf(m);
|
|
382
|
+
if (base && ((meBase && base === meBase) || (lidBase && base === lidBase))) {
|
|
361
383
|
mentioned = true;
|
|
362
384
|
break;
|
|
363
385
|
}
|
|
364
386
|
}
|
|
365
|
-
if (!mentioned && ci && ci.participant
|
|
366
|
-
|
|
387
|
+
if (!mentioned && ci && ci.participant) {
|
|
388
|
+
const pBase = baseOf(ci.participant);
|
|
389
|
+
if ((meBase && pBase === meBase) || (lidBase && pBase === lidBase)) {
|
|
390
|
+
mentioned = true; // mesajımıza reply verilmiş
|
|
391
|
+
}
|
|
367
392
|
}
|
|
393
|
+
/* metin yedeği: @<bot numarası> elle yazılmışsa da mention sayılır */
|
|
394
|
+
if (!mentioned && meBase && t.includes('@' + meBase)) mentioned = true;
|
|
368
395
|
}
|
|
369
396
|
|
|
370
397
|
// medya çıkarımı (varsa)
|
|
@@ -406,6 +433,9 @@ class WhatsAppBridge {
|
|
|
406
433
|
senderNum = (participantJid || jid).split('@')[0].split(':')[0];
|
|
407
434
|
if (!/^\d+$/.test(senderNum)) senderNum = '';
|
|
408
435
|
}
|
|
436
|
+
/* kendi kendine sohbette gönderen = botun telefon numarası — izin listesi
|
|
437
|
+
telefon numarasıyla eşleşsin (LID self-chat'te jid @lid olur) */
|
|
438
|
+
if (selfChat && meBase) senderNum = meBase;
|
|
409
439
|
this.onIncoming(jid, {
|
|
410
440
|
text: t,
|
|
411
441
|
media,
|
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
|
@@ -195,8 +195,10 @@
|
|
|
195
195
|
it_connect: 'QR ile Bağlan',
|
|
196
196
|
it_disconnect: 'Kes',
|
|
197
197
|
it_reset_pair: 'Eşlemeyi Sıfırla',
|
|
198
|
-
it_groups_on: 'WhatsApp gruplarında çalış
|
|
199
|
-
|
|
198
|
+
it_groups_on: 'WhatsApp gruplarında çalış',
|
|
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)',
|
|
201
|
+
it_groups_all: 'Grubun her mesajına karış',
|
|
200
202
|
it_allow_label: 'İzinli numaralar',
|
|
201
203
|
it_name_ph: 'İsim (zorunlu)',
|
|
202
204
|
it_num_ph: 'Numara 905xxxxxxxxx',
|
|
@@ -723,8 +725,10 @@
|
|
|
723
725
|
it_connect: 'Connect via QR',
|
|
724
726
|
it_disconnect: 'Disconnect',
|
|
725
727
|
it_reset_pair: 'Reset pairing',
|
|
726
|
-
it_groups_on: 'Work in WhatsApp groups
|
|
727
|
-
|
|
728
|
+
it_groups_on: 'Work in WhatsApp groups',
|
|
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)',
|
|
731
|
+
it_groups_all: 'Respond to every group message',
|
|
728
732
|
it_allow_label: 'Allowed numbers',
|
|
729
733
|
it_name_ph: 'Name (required)',
|
|
730
734
|
it_num_ph: 'Number 905xxxxxxxxx',
|
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">
|
|
@@ -1498,7 +1498,9 @@ async function renderIntegrationsPane() {
|
|
|
1498
1498
|
</div>
|
|
1499
1499
|
<div style="height:14px"></div>
|
|
1500
1500
|
<label class="lock-row"><input type="checkbox" id="waGroupsOn" ${g.enabled ? 'checked' : ''}/><span>${_t('it_groups_on')}</span></label>
|
|
1501
|
-
<label class="lock-row" style="${g.enabled ? '' : 'opacity:.45'}"><input type="
|
|
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
|
+
<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>
|
|
1502
1504
|
<div class="divider"></div>
|
|
1503
1505
|
<label class="mem-label" style="margin-top:0">${_t('it_allow_label')}</label>
|
|
1504
1506
|
<div id="waAllowChips" class="chips-inline"></div>
|
|
@@ -1571,16 +1573,44 @@ async function renderIntegrationsPane() {
|
|
|
1571
1573
|
</div>`;
|
|
1572
1574
|
|
|
1573
1575
|
const waGroupsOn = $('#waGroupsOn');
|
|
1576
|
+
const waGroupsMention = $('#waGroupsMention');
|
|
1574
1577
|
const waGroupsAll = $('#waGroupsAll');
|
|
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 */
|
|
1582
|
+
const syncGroupMode = () => {
|
|
1583
|
+
const op = waGroupsOn.checked ? '' : '.45';
|
|
1584
|
+
if (waGroupsMention) waGroupsMention.parentElement.style.opacity = op;
|
|
1585
|
+
if (waGroupsAll) waGroupsAll.parentElement.style.opacity = op;
|
|
1586
|
+
if (waSeeAllRow) waSeeAllRow.style.opacity = waGroupsOn.checked && waGroupsMention && waGroupsMention.checked ? '' : '.45';
|
|
1587
|
+
};
|
|
1575
1588
|
const saveGroups = async () => {
|
|
1576
|
-
const r = await beast.waSetGroups({ enabled: waGroupsOn.checked, mentionOnly: !waGroupsAll.checked });
|
|
1577
|
-
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
|
+
);
|
|
1578
1599
|
};
|
|
1579
1600
|
waGroupsOn.addEventListener('change', async () => {
|
|
1580
|
-
|
|
1601
|
+
syncGroupMode();
|
|
1581
1602
|
await saveGroups();
|
|
1582
1603
|
});
|
|
1583
|
-
|
|
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);
|
|
1613
|
+
syncGroupMode();
|
|
1584
1614
|
|
|
1585
1615
|
$('#waStartBtn').addEventListener('click', async () => {
|
|
1586
1616
|
toast('Bağlanıyor…');
|
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
|
+
});
|