beast-agent 0.23.6 → 0.23.7

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": "0.23.6",
4
+ "version": "0.23.7",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
@@ -834,6 +834,51 @@ class Engine {
834
834
  } catch {}
835
835
  }
836
836
 
837
+ /* GÖRSEL DÖNGÜSÜ KORUMASI: görüntü desteklemeyen modele görsel ulaşırsa
838
+ sağlayıcı 400/404 döndürür; görsel oturum geçmişinde kaldığı için sonraki
839
+ HER mesaj aynı hatayla ölür (sonsuz hata döngüsü). Bu metod geçmişteki tüm
840
+ image_url parçalarını metin notuyla değiştirir — hem bellekte hem jsonl'de.
841
+ Döndürür: kaldırılan görsel sayısı */
842
+ _sanitizeSessionImages(session) {
843
+ const NOTE = '[görsel gösterilemedi — bu model görüntü girişini desteklemiyor]';
844
+ const hasImg = (c) => Array.isArray(c) && c.some((p) => p && p.type === 'image_url');
845
+ const clean = (content) => {
846
+ const parts = [];
847
+ for (const p of content) {
848
+ if (p && p.type === 'image_url') continue;
849
+ parts.push(p);
850
+ }
851
+ if (!parts.some((p) => p && p.type === 'text' && String(p.text || '').trim())) {
852
+ parts.push({ type: 'text', text: NOTE });
853
+ }
854
+ return parts;
855
+ };
856
+ let removed = 0;
857
+ for (const m of session.messages) {
858
+ if (hasImg(m.content)) {
859
+ removed += m.content.filter((p) => p && p.type === 'image_url').length;
860
+ m.content = clean(m.content);
861
+ }
862
+ }
863
+ if (!removed) return 0;
864
+ try {
865
+ const file = this._file(session.id);
866
+ const lines = fs.readFileSync(file, 'utf8').split('\n');
867
+ for (let i = 0; i < lines.length; i++) {
868
+ if (!lines[i].trim()) continue;
869
+ let r;
870
+ try { r = JSON.parse(lines[i]); } catch { continue; }
871
+ if (r.t === 'msg' && r.role === 'user' && hasImg(r.content)) {
872
+ lines[i] = JSON.stringify({ t: 'msg', role: 'user', content: clean(r.content) });
873
+ }
874
+ }
875
+ const tmp = file + '.tmp';
876
+ fs.writeFileSync(tmp, lines.join('\n'));
877
+ fs.renameSync(tmp, file);
878
+ } catch {}
879
+ return removed;
880
+ }
881
+
837
882
  _load(id) {
838
883
  if (this.cache.has(id)) return this.cache.get(id);
839
884
  const file = this._file(id);
@@ -2113,7 +2158,7 @@ class Engine {
2113
2158
  `\n\n# OTURUM NOTLARI (oturum ${session.code || '?'} — bu oturumun önceki konuşma özeti; birebir geçmişi buradan hatırla)\n` +
2114
2159
  session.notes;
2115
2160
  }
2116
- const payload = this._buildPayload(system, session.messages, session.notes);
2161
+ let payload = this._buildPayload(system, session.messages, session.notes);
2117
2162
  // Vision model sadece mesajda görsel varsa devreye girer; terminal ise prompt'un
2118
2163
  // shell/command işi olduğunda. Aksi halde ana model kullanılır.
2119
2164
  let role = this._lastUserHasImage(session) ? 'vision' : null;
@@ -2124,16 +2169,48 @@ class Engine {
2124
2169
  if (role && this.roleModels[role]) {
2125
2170
  emitSafe(this, session.id, { type: 'status', status: `rol: ${role} → ${sel.providerName} · ${sel.model}` });
2126
2171
  }
2127
- // FALLOUT destekli çağrı: hata olursa zincirdeki sıradaki sağlayıcıya geç
2128
- const res = await this._streamWithFallbacks(
2129
- session,
2130
- payload,
2131
- activeTools,
2132
- signal,
2133
- onDelta,
2134
- sel,
2135
- role === 'vision'
2136
- );
2172
+ let res;
2173
+ try {
2174
+ // FALLOUT destekli çağrı: hata olursa zincirdeki sıradaki sağlayıcıya geç
2175
+ res = await this._streamWithFallbacks(
2176
+ session,
2177
+ payload,
2178
+ activeTools,
2179
+ signal,
2180
+ onDelta,
2181
+ sel,
2182
+ role === 'vision'
2183
+ );
2184
+ } catch (e) {
2185
+ /* GÖRSEL DÖNGÜSÜ KORUMASI: görüntü desteklemeyen model hata verirse görsel
2186
+ geçmişte kalır ve sonraki HER mesaj aynı hatayla ölür. Oturumu görsellerden
2187
+ arındırıp turu BİR KEZ görüntüsüz dener. */
2188
+ const msgStr = String((e && e.message) || '');
2189
+ const aborted = signal && signal.aborted;
2190
+ const imgish =
2191
+ !aborted &&
2192
+ /image|vision|multimodal|no endpoints/i.test(msgStr);
2193
+ const hasImg =
2194
+ !aborted &&
2195
+ session.messages.some((m) => Array.isArray(m.content) && m.content.some((p) => p && p.type === 'image_url'));
2196
+ if (!imgish || !hasImg) throw e;
2197
+ const removed = this._sanitizeSessionImages(session);
2198
+ if (!removed) throw e;
2199
+ emitSafe(this, session.id, {
2200
+ type: 'status',
2201
+ status: `\u{1F5BC} bu model görüntü girişini desteklemiyor — sohbetten ${removed} görsel kaldırıldı, mesaj görüntüsüz yanıtlanıyor (/change ile görsel destekli modele geçebilirsin)`,
2202
+ });
2203
+ payload = this._buildPayload(system, session.messages, session.notes);
2204
+ res = await this._streamWithFallbacks(
2205
+ session,
2206
+ payload,
2207
+ activeTools,
2208
+ signal,
2209
+ onDelta,
2210
+ sel,
2211
+ false
2212
+ );
2213
+ }
2137
2214
  // Gerçek kullanım ile tahmini bütçeyi kalibre et
2138
2215
  const actual = res.usage && res.usage.prompt_tokens;
2139
2216
  if (actual > 0) {
@@ -411,6 +411,9 @@ class WhatsAppBridge {
411
411
  media,
412
412
  isGroup,
413
413
  participant: participantJid,
414
+ /* LID çağında gerçek telefon: participantAlt; varsa WA kullanıcı adı */
415
+ participantPn: isGroup ? String(msg.key.participantAlt || '') : '',
416
+ participantUsername: isGroup ? String(msg.key.participantUsername || '') : '',
414
417
  mentioned,
415
418
  }, senderNum);
416
419
  } catch {}
package/src/main.js CHANGED
@@ -1164,15 +1164,32 @@ async function waFlush(jid) {
1164
1164
  waLog(`flush: çağrıldı jid=${waPrettyJid(jid)} kuyrukta=${q ? q.payloads.length : 0}`);
1165
1165
  if (!q) return;
1166
1166
  waQueue.delete(jid);
1167
- const merged = { text: '', media: null, isGroup: false, participant: '', mentioned: false };
1167
+ const merged = { text: '', media: null, isGroup: false, participant: '', participantPn: '', participantUsername: '', mentioned: false };
1168
1168
  let senderNum = '';
1169
- /* push tarafıyla AYNI alan adı: { payload, senderNum } */
1169
+ /* push tarafıyla AYNI alan adı: { payload, senderNum }.
1170
+ Gruplarda farklı kişilerden gelen mesajlar birleşirse, ilk kişi label'da
1171
+ adı geçer; DİĞER kişilerin mesajları metne [kim] etiketiyle eklenir. */
1172
+ let firstParticipant = '';
1170
1173
  for (const { payload, senderNum: sn } of q.payloads) {
1171
- if (payload.text) merged.text += (merged.text ? '\n' : '') + payload.text;
1174
+ if (payload.text) {
1175
+ let txt = payload.text;
1176
+ if (payload.isGroup && payload.participant) {
1177
+ const pid = String(payload.participant);
1178
+ if (!firstParticipant) firstParticipant = pid;
1179
+ else if (pid !== firstParticipant) {
1180
+ const tagD = String(payload.participantPn || '').split('@')[0].split(':')[0] || pid.split('@')[0].split(':')[0];
1181
+ txt = `[+${tagD}]: ${txt}`;
1182
+ }
1183
+ }
1184
+ merged.text += (merged.text ? '\n' : '') + txt;
1185
+ }
1172
1186
  if (payload.media && !merged.media) merged.media = payload.media;
1173
1187
  if (payload.isGroup) merged.isGroup = true;
1174
- if (payload.participant) merged.participant = payload.participant;
1175
- if (payload.mentioned) merged.mentioned = true;
1188
+ /* ilk gönderen esas alınır — sonrakiler metne [kim] etiketiyle gelir */
1189
+ if (payload.participant && !merged.participant) merged.participant = payload.participant;
1190
+ if (payload.participantPn && !merged.participantPn) merged.participantPn = payload.participantPn;
1191
+ if (payload.participantUsername && !merged.participantUsername) merged.participantUsername = payload.participantUsername;
1192
+ if (payload.mentioned) merged.mentioned = true;
1176
1193
  if (!senderNum && sn) senderNum = sn;
1177
1194
  }
1178
1195
  try {
@@ -1703,19 +1720,34 @@ async function processWaMessage(jid, payload, senderNum, requeues = 0) {
1703
1720
  waLog(`perm=${fmtPerm(perm)} bot=${botId} sid=${sid}`);
1704
1721
 
1705
1722
  const participantName = payload.participant ? '+' + String(payload.participant).split('@')[0].split(':')[0] : '';
1723
+ /* GRUP GÖNDERENİ: LID çağında gerçek telefon participantAlt'ta gelir.
1724
+ Kimlik çözümleme sırası: izin listesindeki isim → @kullanıcı adı → gerçek PN → LID.
1725
+ Ajan böylece grubun içinde mesajın KİMDEN geldiğini görür. */
1726
+ let groupSender = '';
1727
+ if (isGroup) {
1728
+ const pnDigits = String(payload.participantPn || '').split('@')[0].split(':')[0];
1729
+ const hitP = /^\d+$/.test(pnDigits) ? waFind(pnDigits) : null;
1730
+ const uname = String(payload.participantUsername || '').trim();
1731
+ if (hitP && hitP.name) groupSender = `${hitP.name} (+${pnDigits})`;
1732
+ else if (uname) groupSender = `@${uname}${/^\d+$/.test(pnDigits) ? ' (+' + pnDigits + ')' : ''}`;
1733
+ else if (/^\d+$/.test(pnDigits)) groupSender = '+' + pnDigits;
1734
+ else groupSender = participantName || '';
1735
+ }
1706
1736
  /* #v13.1 rol: SAHİP vs MİSAFİR — ajan kime konuştuğunu net bilsin */
1707
1737
  const isOwner = !isGroup && !!hit.owner;
1708
1738
  const roleTag = isOwner
1709
1739
  ? 'SAHİBİN (talepleri önceliklidir)'
1710
1740
  : 'MİSAFİR (izinli ama sahibin sözü önceliklidir)';
1711
1741
  const label = isGroup
1712
- ? `Grup ${jid.split('@')[0]}${participantName ? ' · ' + participantName : ''}`
1742
+ ? `Grup ${jid.split('@')[0]}${groupSender ? ' gönderen: ' + groupSender : ''}`
1713
1743
  : hit.name
1714
1744
  ? `${hit.name} (+${senderNum || '?'}) — ${roleTag}`
1715
1745
  : `+${senderNum || '?'} — ${roleTag}`;
1716
1746
  let text = `[WhatsApp${isGroup ? ' grup' : ''} — gönderen: ${label}]`;
1717
1747
  if (!isGroup && !isOwner) {
1718
1748
  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.]`;
1749
+ } else if (isGroup) {
1750
+ text += `\n[NOT: Grup mesajı — gönderen grubun üyesidir, izin listendeki kişi olmayabilir. Grup üyelerine karşı temkinli konuş.]`;
1719
1751
  }
1720
1752
  const attachments = [];
1721
1753