beast-agent 2.7.1 → 2.7.2

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/main.js +95 -26
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "beast-agent",
3
3
  "productName": "Beast Agent",
4
- "version": "2.7.1",
4
+ "version": "2.7.2",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
package/src/main.js CHANGED
@@ -234,6 +234,7 @@ let waSingleSid = null; // KANAL TEK OTURUMU: WhatsApp için BİR oturum — sil
234
234
  let waHistory = new Map(); // jid -> [sid,...] bu sohbete ait tüm oturumlar
235
235
  let waBcMode = new Set(); // jid -> BeastCode modu AKTİF (WhatsApp'tan uzaktan kodlama)
236
236
  let waJidPn = new Map(); // jid -> gerçek telefon numarası (LID fallback için)
237
+ let waLastActiveJid = ''; // en son mesaj gelen WA sohbeti — kanal tek oturumunda cevap/dosya hedefi
237
238
  const WA_HISTORY_CAP = 20;
238
239
  let tg = null;
239
240
  let tgChats = new Map(); // telegram chatId -> aktif session id (hepsi KANAL TEK OTURUMUNA bağlı)
@@ -1759,6 +1760,19 @@ function ensureWaSession(jid) {
1759
1760
  return waSingleSid;
1760
1761
  }
1761
1762
 
1763
+ /* Kanal tek oturumunda cevap/dosya hedefi: bu oturuma bağlı sohbetler arasında
1764
+ EN SON mesaj gelen önceliklidir (kullanıcı o pencerede bekliyor); o bilgi
1765
+ yoksa (restart vb.) ilk bağlı sohbet kullanılır. null = bağlı sohbet yok. */
1766
+ function waReplyJid(sid) {
1767
+ try {
1768
+ if (waLastActiveJid && waChats.get(waLastActiveJid) === sid) return waLastActiveJid;
1769
+ } catch {}
1770
+ for (const [j, s] of waChats) {
1771
+ if (s === sid) return j;
1772
+ }
1773
+ return null;
1774
+ }
1775
+
1762
1776
  /* ---------- GRUP BAĞLAM AKIŞI (mentionOnly + seeAll) ----------
1763
1777
  Bot grubun tüm konuşmasını okur ama CEVAP ÜRETMEZ; mesajlar sessizce
1764
1778
  oturum geçmişine bağlam olarak düşer. @mention gelince bot tüm bu
@@ -2311,6 +2325,7 @@ async function processWaMessage(jid, payload, senderNum, requeues = 0) {
2311
2325
  }
2312
2326
  /* KANAL TEK OTURUMU: eskisi silinmedikçe hep aynı oturumdan devam */
2313
2327
  sid = ensureWaSession(jid);
2328
+ waLastActiveJid = String(jid); // cevap/dosya hedefi: en son yazan sohbet
2314
2329
  // Cevap verilecek — karşı telefonda "yazıyor…" göstergesi (medya işlenene dek sürer)
2315
2330
  wa.setComposing(jid, true);
2316
2331
  // Kişi bazlı granül izin: all/web/read/chat
@@ -2998,13 +3013,13 @@ function reloadBackend() {
2998
3013
  Spam olmasın: sohbet başına en az 7 sn'de bir tek satır. */
2999
3014
  if (ev.type === 'tool-start' && wa && wa.connected) {
3000
3015
  try {
3001
- const hit = [...waChats.entries()].find(([, s]) => s === ev.sessionId);
3002
- if (hit) {
3016
+ const pingJid = waReplyJid(ev.sessionId);
3017
+ if (pingJid) {
3003
3018
  const now = Date.now();
3004
3019
  const last = lastWaToolPing.get(ev.sessionId) || 0;
3005
3020
  if (now - last >= 7000) {
3006
3021
  lastWaToolPing.set(ev.sessionId, now);
3007
- sendWaSafe(hit[0], '\u203A ' + String(waToolLine(ev.name, ev.args || {})).replace(/^\u203A /, '')).catch(() => {});
3022
+ sendWaSafe(pingJid, '\u203A ' + String(waToolLine(ev.name, ev.args || {})).replace(/^\u203A /, '')).catch(() => {});
3008
3023
  }
3009
3024
  }
3010
3025
  } catch {}
@@ -3017,8 +3032,8 @@ function reloadBackend() {
3017
3032
  const isInterim = Array.isArray(ev.message.tool_calls) && ev.message.tool_calls.length > 0;
3018
3033
  if (mtxt && isInterim) {
3019
3034
  try {
3020
- const hit = [...waChats.entries()].find(([, s]) => s === ev.sessionId);
3021
- if (hit) sendWaSafe(hit[0], mtxt).catch(() => {});
3035
+ const interJid = waReplyJid(ev.sessionId);
3036
+ if (interJid) sendWaSafe(interJid, mtxt).catch(() => {});
3022
3037
  } catch {}
3023
3038
  }
3024
3039
  }
@@ -3042,9 +3057,8 @@ function reloadBackend() {
3042
3057
  }
3043
3058
  // WhatsApp oturumlarının son cevabını geri gönder (metin + opsiyonel ses)
3044
3059
  if ((ev.type === 'done' || ev.type === 'error') && wa && wa.connected) {
3045
- const hit = [...waChats.entries()].find(([, s]) => s === ev.sessionId);
3046
- if (hit) {
3047
- const wajid = hit[0];
3060
+ const wajid = waReplyJid(ev.sessionId);
3061
+ if (wajid) {
3048
3062
  (async () => {
3049
3063
  try {
3050
3064
  if (ev.type === 'error') {
@@ -3762,8 +3776,9 @@ function setBrowserOpen(v, forceVisible) {
3762
3776
  // AÇMA — görünürlük: forceVisible true/false ise onu uygula;
3763
3777
  // belirtilmemişse kullanıcı tercihi belirler. Tarayıcı gizleme ÖZELLİĞİ
3764
3778
  // (settings.browserHide) kapalıysa tercih ne olursa olsun tarayıcı GÖRÜNÜR açılır.
3765
- // PARALEL AJANLAR (bg oturum) her zaman forceVisible=false ile çağırır →
3766
- // tarayıcı gizli modda çalışır, kullanıcı ekranı ve ajan konsolu rahatsız edilmez.
3779
+ // AJAN gezinmeleri setBrowserOpenForAgent üzerinden gelir: gizleme özelliği
3780
+ // kapalıysa görünür, özellik + göz kapalıyken gizli çalışır (kullanıcı ekranı
3781
+ // rahatsız edilmez; tarayıcı düğmesi gizli paneli görünür kılar).
3767
3782
  browser.open = true;
3768
3783
  browser.visible =
3769
3784
  forceVisible === true ? true : forceVisible === false ? false : !browserHeadlessPref();
@@ -3796,13 +3811,15 @@ function browserGate(job) {
3796
3811
  return p;
3797
3812
  }
3798
3813
  /* AJAN TARAYICI STRATEJİSİ — TÜM oturumlar (ana sohbet + WhatsApp botları + paralel ajanlar):
3799
- ajan tarayıcıyı KENDİ açıyorsa hep GİZLİ açılır — panel ekrana fırlamaz,
3800
- Paralel Ajan Konsolu kapanmaz. Kullanıcı izlemek isterse tarayıcı düğmesine
3801
- basar (browser:toggle gizli paneli görünür kılar). Zaten açıksa (kullanıcı
3802
- paneli açık tutuyorsa) görünürlüğe dokunulmaz. */
3814
+ GİZLEME ÖZELLİĞİ (Ayarlar → Web Arama) KAPALIYSA ajan tarayıcıyı GİZLİ açamaz —
3815
+ dahili panel GÖRÜNÜR açılır, kullanıcı aramayı canlı izler. Özellik açıksa göz
3816
+ durumuna bakılır: göz açık (headless tercihi kapalı) → görünür, göz kapalı →
3817
+ gizli. Mobil önizleme varsa siluet canlı kalsın diye her koşulda görünür.
3818
+ Zaten açıksa (kullanıcı paneli açık tutuyorsa) görünürlüğe dokunulmaz. */
3803
3819
  function setBrowserOpenForAgent() {
3804
- /* mobil önizleme açıkken ajan gezinmeleri GÖRÜNÜR açılır — siluet canlı kalsın */
3805
- if (!browser.open) setBrowserOpen(true, browser.mobile ? true : false);
3820
+ if (!browser.open) {
3821
+ setBrowserOpen(true, !browserHideEnabled() || !browserHeadlessPref() || browser.mobile);
3822
+ }
3806
3823
  }
3807
3824
 
3808
3825
  async function browserNavigate(raw, signal, ctx) {
@@ -6761,7 +6778,8 @@ ipcMain.handle('cron:runNow', (_e, id) => {
6761
6778
  /* ---------------- tarayıcı IPC ---------------- */
6762
6779
  ipcMain.handle('browser:toggle', () => {
6763
6780
  /* gizli çalışan ajan paneli varsa düğme ONU GÖRÜNÜR yapar; değilse aç/kapa.
6764
- (ajanlar tarayıcıyı hep gizli açar — kullanıcı izlemek isterse buradan gösterir) */
6781
+ (ajan gizleme özelliği + göz kapalıyken gizli çalışır — kullanıcı izlemek
6782
+ isterse buradan gösterir) */
6765
6783
  if (browser.open && !browser.visible) {
6766
6784
  setBrowserOpen(true, true);
6767
6785
  return { open: browser.open, visible: browser.visible };
@@ -8876,8 +8894,42 @@ ipcMain.handle('screen:capture', async () => {
8876
8894
  });
8877
8895
 
8878
8896
  /* #26 ajanın send_file aracı: dosyayı doğru kanala (WA veya chat) ulaştırır.
8879
- Paralel ajan işiyse parent sohbete, masaüstünde dosya kartı olarak düşer. */
8897
+ Paralel ajan işiyse parent sohbete, masaüstünde dosya kartı olarak düşer.
8898
+ WhatsApp hedefinde resim/video/ses → medya mesajı, PDF/belge → doğru
8899
+ mimetype ile belge mesajı olarak gider; LID adresi patlarsa gerçek numaraya
8900
+ (@s.whatsapp.net) tek kez düşülür, yine başarısızsa ajan'a HATA döner. */
8880
8901
  const FILE_IMAGE_EXT = new Set(['.png', '.jpg', '.jpeg', '.webp', '.gif']);
8902
+ const FILE_MIME = {
8903
+ '.pdf': 'application/pdf',
8904
+ '.doc': 'application/msword',
8905
+ '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
8906
+ '.xls': 'application/vnd.ms-excel',
8907
+ '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
8908
+ '.ppt': 'application/vnd.ms-powerpoint',
8909
+ '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
8910
+ '.txt': 'text/plain',
8911
+ '.csv': 'text/csv',
8912
+ '.md': 'text/markdown',
8913
+ '.json': 'application/json',
8914
+ '.html': 'text/html',
8915
+ '.zip': 'application/zip',
8916
+ '.rar': 'application/vnd.rar',
8917
+ '.7z': 'application/x-7z-compressed',
8918
+ '.mp3': 'audio/mpeg',
8919
+ '.m4a': 'audio/mp4',
8920
+ '.wav': 'audio/wav',
8921
+ '.ogg': 'audio/ogg',
8922
+ '.opus': 'audio/ogg; codecs=opus',
8923
+ '.mp4': 'video/mp4',
8924
+ '.mkv': 'video/x-matroska',
8925
+ '.webm': 'video/webm',
8926
+ '.svg': 'image/svg+xml',
8927
+ '.apk': 'application/vnd.android.package-archive',
8928
+ };
8929
+ function mimeForFile(ext) {
8930
+ return FILE_MIME[String(ext || '').toLowerCase()] || 'application/octet-stream';
8931
+ }
8932
+
8881
8933
  async function deliverFile(sessionId, filePath, caption) {
8882
8934
  try {
8883
8935
  const abs = path.isAbsolute(filePath) ? filePath : path.join(engine.workspace || process.cwd(), filePath);
@@ -8889,17 +8941,34 @@ async function deliverFile(sessionId, filePath, caption) {
8889
8941
  const job = engine.listBgJobs().find((j) => j.id === sid);
8890
8942
  if (job && job.parentId) sid = job.parentId; // paralel ajan → parent sohbet
8891
8943
  } catch {}
8892
- /* WhatsApp hedefi: bu oturum bir WA sohbetine bağlıysa oraya gönder */
8893
- let jid = null;
8894
- for (const [j, s] of waChats) {
8895
- if (s === sid) { jid = j; break; }
8896
- }
8944
+ /* WhatsApp hedefi: bu oturum bir WA sohbetine bağlıysa oraya gönder.
8945
+ Birden çok sohbet aynı oturuma bağlıysa EN SON yazan kazanır. */
8946
+ const jid = waReplyJid(sid);
8897
8947
  if (jid && wa) {
8898
8948
  const buf = fs.readFileSync(abs);
8899
- const ok = FILE_IMAGE_EXT.has(ext)
8900
- ? await wa.sendImage(jid, buf, caption || name)
8901
- : await wa.sendFile(jid, buf, name, caption);
8949
+ const sendMedia = async (target) =>
8950
+ FILE_IMAGE_EXT.has(ext)
8951
+ ? await wa.sendImage(target, buf, caption || name)
8952
+ : await wa.sendFile(target, buf, name, caption, mimeForFile(ext));
8953
+ let ok = false;
8954
+ try { ok = !!(await sendMedia(jid)); } catch {}
8955
+ if (!ok) {
8956
+ /* LID/adres fallback: bilinen gerçek numaraya (@s.whatsapp.net) dene */
8957
+ const pn = waJidPn.get(jid);
8958
+ const pnJid = pn ? `${pn}@s.whatsapp.net` : '';
8959
+ if (pnJid && pnJid !== jid) {
8960
+ try { ok = !!(await sendMedia(pnJid)); } catch {}
8961
+ if (ok) waLog(`dosya fallback → ${waPrettyJid(pnJid)} (lid/pn denemelerinden sonra)`);
8962
+ }
8963
+ }
8902
8964
  if (ok) return { ok: true, channel: 'whatsapp', name };
8965
+ /* WA hedefi var ama gönderim başarısız — sessizce "masaüstüne düştü"
8966
+ SANMA: ajan durumu bilsin, kullanıcıya söyleyip tekrar deneyebilsin */
8967
+ waLog(`dosya gönderim HATA → ${waPrettyJid(jid)} "${name}"`);
8968
+ return {
8969
+ ok: false,
8970
+ error: `whatsapp gönderimi başarısız: ${name} — bağlantı/medya hatası; biraz sonra tekrar dene ya da kullanıcıya söyle`,
8971
+ };
8903
8972
  }
8904
8973
  /* masaüstü: sohbete dosya kartı bas */
8905
8974
  if (win && !win.isDestroyed()) {