beast-agent 2.38.4 → 2.40.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.
Files changed (41) hide show
  1. package/package.json +1 -1
  2. package/src/agent/bots.js +66 -0
  3. package/src/agent/customtools.js +52 -2
  4. package/src/agent/defaulttools/mt5_barlar2/run.js +188 -0
  5. package/src/agent/defaulttools/mt5_barlar2/tool.json +23 -0
  6. package/src/agent/defaulttools/mt5_bekleyen/run.js +100 -0
  7. package/src/agent/defaulttools/mt5_bekleyen/tool.json +17 -0
  8. package/src/agent/defaulttools/mt5_durum/run.js +93 -0
  9. package/src/agent/defaulttools/mt5_durum/tool.json +10 -0
  10. package/src/agent/defaulttools/mt5_emir/run.js +98 -0
  11. package/src/agent/defaulttools/mt5_emir/tool.json +17 -0
  12. package/src/agent/defaulttools/mt5_emir_iptal/run.js +90 -0
  13. package/src/agent/defaulttools/mt5_emir_iptal/tool.json +12 -0
  14. package/src/agent/defaulttools/mt5_fiyat/run.js +87 -0
  15. package/src/agent/defaulttools/mt5_fiyat/tool.json +16 -0
  16. package/src/agent/defaulttools/mt5_gecmis/run.js +87 -0
  17. package/src/agent/defaulttools/mt5_gecmis/tool.json +12 -0
  18. package/src/agent/defaulttools/mt5_kapat/run.js +92 -0
  19. package/src/agent/defaulttools/mt5_kapat/tool.json +13 -0
  20. package/src/agent/defaulttools/mt5_m15_m5/m15m5.py +60 -0
  21. package/src/agent/defaulttools/mt5_m15_m5/run.js +73 -0
  22. package/src/agent/defaulttools/mt5_m15_m5/tool.json +10 -0
  23. package/src/agent/defaulttools/mt5_pozisyon_gecmis/run.js +144 -0
  24. package/src/agent/defaulttools/mt5_pozisyon_gecmis/tool.json +12 -0
  25. package/src/agent/defaulttools/mt5_shot/run.js +428 -0
  26. package/src/agent/defaulttools/mt5_shot/tool.json +20 -0
  27. package/src/agent/defaulttools/mt5_sltp/run.js +92 -0
  28. package/src/agent/defaulttools/mt5_sltp/tool.json +14 -0
  29. package/src/agent/engine.js +259 -47
  30. package/src/agent/financetools.js +52 -0
  31. package/src/agent/mt5/BeastFinance.mq5 +427 -0
  32. package/src/agent/mt5/BeastShot.tpl +0 -0
  33. package/src/agent/mt5setup.js +542 -0
  34. package/src/agent/scripts/mt5_bridge.py +112 -0
  35. package/src/agent/seeds/mql5/SKILL.md +27 -2
  36. package/src/agent/seeds/tool-yazma/SKILL.md +6 -4
  37. package/src/main.js +233 -13
  38. package/src/preload.js +1 -0
  39. package/src/renderer/index.html +18 -5
  40. package/src/renderer/renderer.js +170 -37
  41. package/src/renderer/style.css +45 -17
@@ -132,15 +132,16 @@ const PERM_TOOL_SETS = {
132
132
  'web_search', 'http_fetch', 'webfetch', 'deep_search',
133
133
  'browser_open', 'browser_read', 'browser_snapshot', 'browser_screenshot',
134
134
  'browser_click', 'browser_type', 'browser_press', 'browser_scroll', 'browser_select',
135
- 'ocr_read',
135
+ 'ocr_read', 'tool_request',
136
136
  ]),
137
137
  read: new Set([
138
138
  'web_search', 'http_fetch', 'webfetch', 'deep_search',
139
139
  'browser_open', 'browser_read', 'browser_snapshot',
140
140
  'list_dir', 'read_file', 'grep', 'glob',
141
141
  'git_diff_review', 'repo_map', 'repo_symbols', 'xlsx_read',
142
+ 'tool_request',
142
143
  ]),
143
- chat: new Set([]), // sadece sohbet
144
+ chat: new Set(['tool_request']), // sohbet + tool devri
144
145
  };
145
146
  const PERM_LEVELS = ['all', 'web', 'read', 'chat'];
146
147
 
@@ -320,6 +321,8 @@ class Engine {
320
321
  this.browser = opts.browser || null; // dahili tarayıcı kancaları
321
322
  this.fileSend = opts.fileSend || null; // #26 dosya gönderim köprüsü (chat/WA)
322
323
  this.notifyOwnerFail = opts.notifyOwnerFail !== false; // #25 hata mail bildirimi (runtime /notify)
324
+ /* ENTEGRASYON BİLDİRİMİ: tool yazım/doğrulama adımları WA/TG/Discord'a düşer */
325
+ this.integrationNotify = typeof opts.integrationNotify === 'function' ? opts.integrationNotify : null;
323
326
  this.reminders = opts.reminders || null; // hatırlatıcı kancası (main enjekte eder)
324
327
  this.watchers = opts.watchers || null; // arka plan izleyici köprüsü (main enjekte eder)
325
328
  /* opencode permission Service (birebir port): ask/reply — once|always|reject.
@@ -399,6 +402,7 @@ class Engine {
399
402
  if (nrBoot.unref) nrBoot.unref();
400
403
  skills.seedIfEmpty();
401
404
  agentdefs.seedIfEmpty();
405
+ try { customtools.seedDefaults(); } catch {} // varsayılan MT5 toolları (mt5_shot, mt5_barlar2, ...) — tüm oturumlara açılır
402
406
  }
403
407
 
404
408
  /* ---------- opencode agent.ts port: özel ajanlar ---------- */
@@ -1915,9 +1919,11 @@ class Engine {
1915
1919
  risk:
1916
1920
  'ROL: RİSK AJANI 🛡 — portföyün RİSK GÖZCÜSÜSÜN, İşlem AÇMAZSIN (mt5_trade/mt5_pending/mt5_close KULLANMA). Her turda: hesap + pozisyonlar + marj kullanımı → kaldıraç/exposure değerlendirmesi; SL\u2019siz pozisyon, limit aşımı, tek yönlü birikme, günlük kayıp hızı risklerini raporla; net düzeltme önerisi ver (hangi pozisyon küçültülmeli/kapatılmalı).',
1917
1921
  technic:
1918
- 'ROL: TEKNİK ANALİZ AJANI 📊 — fiyat yapısı uzmanısın, İşlem AÇMAZSIN (mt5_trade/mt5_pending/mt5_close KULLANMA). Her turda odak semboller için: trend/yapı, destek-direnç bölgeleri, momentum (mt5_market + mt5_history verisiyle); sembol başına AL/SAT/BEKLE + giriş/SL/TP fikri üret — trader bu öneriyi işleme çevirir.',
1922
+ 'ROL: TEKNİK ANALİZ AJANI 📊 — fiyat yapısı uzmanısın, İşlem AÇMAZSIN (mt5_trade/mt5_pending/mt5_close KULLANMA). Her turda odak semboller için: trend/yapı, destek-direnç bölgeleri, momentum (mt5_market + mt5_history verisiyle); sembol başına AL/SAT/BEKLE + giriş/SL/TP fikri üret — trader bu öneriyi işleme çevirir. Bulgularını mt5_ea action:"note" ile grafiğe ÇİZ (destek/direnç/SL/TP seviyeleri) ve tool__mt5_shot ile seviyelerin göründüğünü teyit et; önemli seviyeleri görselle birlikte agent_dm ile trader\'a/ekibe ilet — grafik üzerinde ortak karar alın.',
1919
1923
  macro:
1920
1924
  'ROL: MAKRO AJANI 🌍 — büyük resim uzmanısın, İşlem AÇMAZSIN (mt5_trade/mt5_pending/mt5_close KULLANMA). Her turda web_search ile güncel makro manşetleri + ekonomik takvim riskleri (faiz, CPI, jeopolitik); DXY/altın/petrol bağıntılarını odak sembollere çevir; sembol başına yön eğilimi + TEMKİN/BEKLE notu ver.',
1925
+ visual:
1926
+ 'ROL: GÖRSEL ANALİZ AJANI 👁 — grafik/ekran görüntüsü uzmanısın, İşlem AÇMAZSIN (mt5_trade/mt5_pending/mt5_close KULLANMA). MT5 grafiği için ÖNCE tool__mt5_shot kullan (BeastFinance EA "shot": gerçek grafik PNG + görsel SONRAKİ TURDA gözüne gelir; symbol/timeframe vererek başka sembol/periyot çekebilirsin; dönen path send_file ile gönderilebilir); gerekirse EA panosuna mt5_ea action:"note" ile plan/seviye yaz. web grafiklerinde browser_open + browser_screenshot; bilgisayar ekranı için computer_look; görsel göremeyen metin-model isen ocr_read (source:"screen"/"browser") kullan. Gördüğün yapıyı yorumla: trend, formasyon, mum yapısı, destek-direnç ve SL/TP çizgileri; sembol başına GÖRSEL TEYİT + AL/SAT/BEKLE notu üret. Önemli bulguyu GÖRSELİYLE paylaş: agent_dm image:true (son ekran görüntün) ya da image:"<mt5_shot path>" ile ANA TRADER\'a/ekibe gönder — karşı ajan görüntüyü GERÇEKTEN görür, AJAN DM panelinde açılır.',
1921
1927
  })[String((session && session.financeRole) || '')] || '';
1922
1928
  /* ROL → SKILL eşleştirmesi (ayarlar modalı): rol başına TEK ve ZORUNLU
1923
1929
  skill; ANA TRADER için ayrıca PLAYBOOK skill'i (roleSkills.trader).
@@ -1956,12 +1962,23 @@ class Engine {
1956
1962
  modeBlock2 + '\n' +
1957
1963
  (roleBlockFull ? roleBlockFull + '\n' : '') +
1958
1964
  (teamLine || '') +
1959
- 'MT5 ARAÇLARI: mt5_status (bağlantı), mt5_account (hesap), mt5_market (canlı fiyat), mt5_positions (açık pozisyonlar), mt5_orders (bekleyen emirler), mt5_history (kapanan işlemler), mt5_trade (piyasa emri), mt5_close (kapat), mt5_modify (SL/TP), mt5_pending (bekleyen emir), mt5_cancel (emir iptal).\n' +
1965
+ 'MT5 ARAÇLARI: mt5_status (bağlantı), mt5_account (hesap), mt5_market (canlı fiyat), mt5_positions (açık pozisyonlar), mt5_orders (bekleyen emirler), mt5_history (kapanan işlemler), mt5_ea (BeastFinance grafik panosu: status/ping/chart/note), mt5_trade (piyasa emri), mt5_close (kapat), mt5_modify (SL/TP), mt5_pending (bekleyen emir), mt5_cancel (emir iptal).\n' +
1966
+ 'KİŞİSEL MT5 ARAÇLARI (VARSAYILAN KURULU — tool__* tüm oturumlarda çağrılabilir): tool__mt5_shot (grafikten GERÇEK PNG + görsel ajana enjekte — grafik işlerinde BİRİNCİL), tool__mt5_barlar2 (OHLC mumlar, köprüsüz), tool__mt5_m15_m5 (GOLD M15+M5 hazır paket), tool__mt5_fiyat, tool__mt5_durum (hesap+pozisyon+emir tek çağrı), tool__mt5_gecmis, tool__mt5_pozisyon_gecmis, tool__mt5_sltp, tool__mt5_kapat, tool__mt5_bekleyen, tool__mt5_emir, tool__mt5_emir_iptal.\n' +
1967
+ 'BEASTFINANCE EA (OTOMATİK GRAFİK UZMANI — ENTEGRASYON KANALI): MT5 terminaline bağlanıldığında BeastFinance uzman danışmanı İLK GRAFİĞE OTOMATİK yüklenir, AutoTrading izni açılır (kurulum sistem tarafından yapılır; elle ekleme gerekmez). Grafik panosu + seviye çizgileri + dosya köprüsü (beast_ea.json / beast_cmd.json / beast_note.json) bu EA üzerinden yürür: entegrasyon işlerinde (status/ping/chart/note) mt5_ea kullan; ekran görüntüsü işlerinde tool__mt5_shot ("shot" komutu — gerçek PNG + ajan görseli) birincildir. Yazdığın not ve çizgiler tool__mt5_shot ve computer_look screenshot\'ında GÖRÜNÜR.\n' +
1960
1968
  'YETKİLERİN (AÇIK — çekinmeden kullan):\n' +
1961
1969
  '- skill: kurulu SKILL.md kataloğunu oku ve uygula — tool yazmadan ÖNCE skill("tool-yazma"), MT5 tarafı işlerden ÖNCE skill("mql5") oku ve prosedürüne birebir uy.\n' +
1962
1970
  '- Kişisel tool yazma: %APPDATA%\\beast\\tools\\<slug>\\ içine tool.json + run.js yaz (write_file/edit_file); run_command ile çıktısını (JSON, ok alanlı) doğrula → tool__<slug> ANINDA tüm finance ajanlarında çağrılabilir olur.\n' +
1963
1971
  '- MQL5: MT5 tarafında script/gösterge/EA yaz (write_file), metaeditor64.exe /compile ile derle, MQL5\\Files dosya köprüsüyle veriyi Beast\'e taşı; kullanıcıya çalıştırma adımını açıkça söyle.\n' +
1964
- '- Yerleşik araçlar: run_command, python_run, read_file/write_file/edit_file, web_search/deep_search, browser_* — hepsi açık.\n' +
1972
+ '- Yerleşik araçlar: run_command, python_run, read_file/write_file/edit_file, web_search/deep_search, browser_*, computer_look (ekran görüntüsü) — hepsi açık.\n' +
1973
+ 'GRAFİK & GÖRSEL DOĞRULAMA (karar öncesi — vazgeçilmez):\n' +
1974
+ '- tool__mt5_shot: MT5 grafiğinin GERÇEK PNG\'si (BeastFinance EA "shot" komutu). Görsel SONRAKİ TURDA gözüne gelir; aktif grafikte Beast panosu + mt5_ea note ile çizilen seviyeler GÖRÜNÜR. symbol/timeframe ver → EA geçici grafik açıp o sembol/periyottan çeker; dönen path send_file ile kullanıcıya gönderilebilir. Grafik analizinde İLK tercih budur.\n' +
1975
+ '- mt5_ea action:"note": grafiğe kısa plan metni + yatay seviye çizgileri yazar (destek/direnç/SL/TP) — sonra tool__mt5_shot ile çekip seviyelerin doğru yerde olduğunu GÖRSEL doğrula.\n' +
1976
+ '- computer_look: tüm masaüstü ekranı (MT5 dışı pencereler dahil); browser_screenshot: web grafikleri (TradingView/Investing) — browser_open ile aç.\n' +
1977
+ '- Görsel göremiyorsan (metin-model) ocr_read (source:"screen"/"browser") ile grafikteki fiyat/seviyeleri metne çevir.\n' +
1978
+ '- Grafikte gezinme/ölçek: computer_act ile timeframe, scroll, zoom (arka planda çalışır — kullanıcının faresini çalmaz).\n' +
1979
+ '- EKİPLE GÖRSEL PAYLAŞIM: chart PNG\'sini agent_dm image:true (son görüntün) ya da image:"<dosya yolu>" (ör. tool__mt5_shot dönen path) ile trader/ekibe gönder — karşı ajan görüntüyü GERÇEKTEN görür, AJAN DM panelinde açılır. Ortak kararlarda grafiği paylaşmak zorunlu disiplindir.\n' +
1980
+ '- PARALEL EKİP AKIŞI: teknik ajan seviyeleri hesaplar → mt5_ea note ile grafiğe çizer; görsel ajan tool__mt5_shot ile çekip TEYİT eder ve agent_dm ile görseli trader\'a atar; risk ajanı aynı görsel üzerinden SL/TP yerleşimini denetler; trader kararı işleme çevirir. Aynı sembolde koşan ajanlar tek DM grubunda (agent_dm group) toplanır — çelişen hareket etme.\n' +
1981
+ '- EKSİK ARAÇ: özel grafik aracı gerekiyorsa (ör. çoklu timeframe tek görüntü, indikatör paneli okuma) VAR SAYMA — tool_request ile TOOL botuna yazdır (doğrulanınca tool__<ad> olarak ANINDA çağrılır); küçük aracı skill("tool-yazma") prosedürüyle kendin de yazabilirsin.\n' +
1965
1982
  'VERİ AKIŞI (her değerlendirmede): mt5_account + mt5_positions + mt5_market çağrılarını AYNI turda PARALEL ver; gerekiyorsa mt5_history ile son işlemleri gör.\n' +
1966
1983
  'PARALEL + KOORDİNASYON: uzun araştırma/işleri run_background ile paralel finance işçisine devret (parent finance olduğu için işçi mt5 okuma araçlarını görür); koşan ajanlarla konuşmak için agent_dm (to: ajan başlığındaki anahtar kelime, örn "GOLD"; ortak karar için group: "İSİM" ile grup sohbeti kur — mesaj tüm üyelere düşer). Görevin bitince DM/grup sohbetleri otomatik KAPANIR (geçmiş panelde kalır).\n' +
1967
1984
  (symbols ? `İZLEME LİSTESİ: ${symbols}\n` : '') +
@@ -2031,6 +2048,14 @@ class Engine {
2031
2048
  '# SKILLS\nKullanmadan önce ilgili SKILL.md dosyasını read_file ile oku.\n' + sk.join('\n')
2032
2049
  );
2033
2050
  }
2051
+ /* BOTLAR: MT5/Beast Finance entegrasyon kanalı — BeastFinance EA'sı otomatik
2052
+ kurulur; botlar grafik/entegrasyon işlerinde bunu bilerek hareket etsin */
2053
+ if (bctx) {
2054
+ parts.push(
2055
+ '# MT5 / BEAST FINANCE ENTEGRASYONU\n' +
2056
+ 'Beast Finance açıldığında MT5 terminaline bağlanılır; BeastFinance.mq5 uzman danışmanı İLK GRAFİĞE OTOMATİK kurulur ve AutoTrading açılır. Grafik/entegrasyon işleri bu EA üzerinden yürür (heartbeat beast_ea.json, komut köprüsü beast_cmd.json, grafik panosu/seviyeler beast_note.json; finance oturumlarında mt5_ea status/ping/chart/note). Grafik görseli için varsayılan kurulu tool__mt5_shot kullan (EA "shot" komutu: gerçek PNG + görsel ajana enjekte edilir; path send_file/agent_dm ile paylaşılabilir) — pano ve çizgiler bu screenshot\'ta görünür. Elle EA ekleme varsayma; eksik araç gerekiyorsa Tool botuna tool_request at.'
2057
+ );
2058
+ }
2034
2059
  /* opencode instruction port (ajan modu): workspace AGENTS/CLAUDE/CONTEXT
2035
2060
  talimatları — oturum başına bir kez okunur, epoch boyunca sabit */
2036
2061
  const projChat = this._projectInstructions(session);
@@ -2072,6 +2097,7 @@ class Engine {
2072
2097
  'Kullanıcı bir tarihte/saatte hatırlatılmasını isterse set_reminder kullan; when değerini ORTAMdaki bugüne göre hesapla (yerel saat). "Her sabah/gün/hafta" gibi tekrarlı isteklerde repeat alanını da ver (daily/weekly/monthly/weekdays veya cron).',
2073
2098
  'Kullanıcı kalıcı bir arka plan takibi isterse (fiyat eşiği, pil seviyesi, sayfa değişikliği) watcher_add ile izleyici kur; kurduktan sonra watcher_list ile doğrula ve kullanıcıya koşulu + kontrol sıklığını kısaca bildir.',
2074
2099
  'Anlık olay takipleri için (yeni mail, fiyat eşiği, dosya değişimi, webhook) event_subscribe kullan — cron/polling gerekmez; listeyi event_list ile göster, vazgeçirirse event_unsubscribe.',
2100
+ 'ARAÇ EKSİKSE ÇEKİNME (CEPHANE): ihtiyacın olan bir araç yoksa/bozuksa tool_request ile TOOL botuna yazdır — tool yazımı onun TEK işidir; task alanına araç adı + ne yapacağı + girdi/çıktı sözleşmesini, context alanına örnek veri/yol/endpoint yaz. Araç doğrulanınca tool__<ad> olarak ANINDA çağrılabilir. Küçük kişisel araçları skill("tool-yazma") prosedürüyle kendin de yazabilirsin — yazdığını run_command ile MUTLAKA doğrula.',
2075
2101
  'LOG ZEKASI: "hata var mı / ne oldu / neden çalışmadı" sorularında ya da bir iş beklenmedik bittiğinde/hata verdiğini fark ettiğinde log_analyze ile logları tara — top desenleri okuyup en olası KÖK NEDENİ tek cümlede söyle, somut çözüm öner; aynı desen 3+ tekrarlıysa bunu vurgula. Sürekli log gözetimi istenirse watcher_add ile kind:"logs" kur (örn "hata artarsa bağır" → level:"error", windowMin:10, op:"gt", value:2) ve koşulu kısaca bildir.',
2076
2102
  'Kullanıcı "artık hep böyle yap / bunu unutma" tarzı kalıcı talimat verirse kural olarak kaydet: sohbette /rule <metin> kullanmasını söyle VEYA kullanıcı isterse event_subscribe ile olaya bağlan (mail/fiyat/dosya/webhook).',
2077
2103
  'Kullanıcının mesajında 2+ ayrı iş/hedef varsa (örn "X yap ve sonra Y\u2019i kontrol et") KODLAMAYA/İŞE BAŞLAMADAN önce todo_write ile plan çıkar ve sırayla yürüt; her adımı tamamlarken güncelle. LİSTE DİSİPLİNİ: her adım bittiği AN status:"done" yap; son cevabını vermeden önce tüm maddeler done olmalı — yapılmayacaksa listeden düş. Listeyi yarım bırakma.',
@@ -2753,14 +2779,30 @@ class Engine {
2753
2779
  const jobRec = this._bgJobs && this._bgJobs.get(String(sessionId));
2754
2780
  if (jobRec && Array.isArray(jobRec.dmInbox) && jobRec.dmInbox.length) {
2755
2781
  const inbox = jobRec.dmInbox.splice(0);
2756
- const boxText = inbox.join('\n\n').slice(0, 6000);
2782
+ const boxText = inbox
2783
+ .map((x) => (typeof x === 'string' ? x : String((x && x.text) || '')))
2784
+ .join('\n\n')
2785
+ .slice(0, 6000);
2786
+ /* görselli DM'ler: son 2 görsel bu turun mesajına vision olarak eklenir */
2787
+ const inboxImgs = inbox
2788
+ .map((x) => (x && typeof x === 'object' && /^data:image\//i.test(String(x.image || '')) ? String(x.image) : ''))
2789
+ .filter(Boolean)
2790
+ .slice(-2);
2757
2791
  const prefix =
2758
2792
  `[BEKLEYEN AJAN DM'LERİ — ${inbox.length} mesaj]\n${boxText}\n` +
2759
2793
  `(Tur aralığında biriken DM'ler. Bu turda değerlendir; yalnızca aksiyon/cevap GEREKİYORSA agent_dm ile TEK kısa cevap ver — teşekkür/onay/ack mesajı YAZMA.)\n\n`;
2760
2794
  if (typeof msg.content === 'string') {
2761
- msg.content = prefix + msg.content;
2762
- } else if (Array.isArray(msg.content) && msg.content[0] && typeof msg.content[0].text === 'string') {
2763
- msg.content[0] = { ...msg.content[0], text: prefix + msg.content[0].text };
2795
+ msg.content = inboxImgs.length
2796
+ ? [
2797
+ { type: 'text', text: prefix + msg.content },
2798
+ ...inboxImgs.map((u) => ({ type: 'image_url', image_url: { url: u } })),
2799
+ ]
2800
+ : prefix + msg.content;
2801
+ } else if (Array.isArray(msg.content)) {
2802
+ if (msg.content[0] && typeof msg.content[0].text === 'string') {
2803
+ msg.content[0] = { ...msg.content[0], text: prefix + msg.content[0].text };
2804
+ }
2805
+ for (const u of inboxImgs) msg.content.push({ type: 'image_url', image_url: { url: u } });
2764
2806
  }
2765
2807
  }
2766
2808
  } catch {}
@@ -3604,13 +3646,21 @@ class Engine {
3604
3646
  if (this._deletedSessions && this._deletedSessions.has(sessionId)) continue;
3605
3647
  if (continuous) {
3606
3648
  job.dmInbox = job.dmInbox || [];
3607
- job.dmInbox.push(String(r.text || '').slice(0, 4000));
3649
+ job.dmInbox.push({
3650
+ text: String(r.text || '').slice(0, 4000),
3651
+ ...(r.image ? { image: String(r.image) } : {}),
3652
+ });
3608
3653
  if (job.dmInbox.length > 20) job.dmInbox.splice(0, job.dmInbox.length - 20);
3609
3654
  continue;
3610
3655
  }
3611
3656
  if (this.isBusy(sessionId)) { rest.push(r); continue; }
3612
3657
  let sent = false;
3613
- try { sent = !!this.send(sessionId, { text: r.text }); } catch {}
3658
+ try {
3659
+ const payload = { text: r.text };
3660
+ /* görselli DM: hedef ajan görüntüyü gerçekten GÖRSÜN (vision mesajı) */
3661
+ if (r.image) payload.attachments = [{ type: 'image', name: 'ekran goruntusu', dataUrl: String(r.image) }];
3662
+ sent = !!this.send(sessionId, payload);
3663
+ } catch {}
3614
3664
  if (!sent) rest.push(r);
3615
3665
  }
3616
3666
  this._pendingReports = rest;
@@ -3630,6 +3680,7 @@ class Engine {
3630
3680
  `DOSYA KURALI: dosya işlemlerinde özel araçları kullan — VAR OLAN dosyayı edit_file ile düzenle, yeniyi write_file ile yaz, okuma/arama read_file/grep/glob; run_command terminal işlerindir (build, git, kurulum). Bir dosyayı BİR KEZ oku — içerik bağlamda kalır, tekrar okuma.\n` +
3631
3681
  (proj ? `PROJE TALİMATLARI (workspace AGENTS/CLAUDE/CONTEXT — daima uy):\n${proj}\n` : '') +
3632
3682
  `HIZ KURALLARI:\n` +
3683
+ `- TOOL İSTEĞİ (CEPHANE): ihtiyacın olan araç yoksa/bozuksa tool_request ile TOOL botuna yazdır (araç doğrulanınca tool__<ad> olarak anında çağrılır); küçük aracı skill("tool-yazma") ile kendin de yaz ve run_command ile doğrula.\n` +
3633
3684
  `- Döngülü işleri (çok URL/dosya/sayfa, tekrarlı parse-hesap) TEK python_run betiğinde topluca bitir.\n` +
3634
3685
  `- Web için web_search kullan (zincir dahili tarayıcıyla başlar — gerçek Chromium ile Google); tek aramada bulunamazsa veya çok kaynaklı derin araştırma gerekiyorsa deep_search kullan (çoklu sorgu paralel + gizli tarayıcıda tam sayfa okuma). Sayfa açma/göstermenin VARSAYILANI DAHİLİ tarayıcıdır: browser_open → browser_snapshot → browser_click/type/read. Kullanıcı açıkça dış tarayıcı (chrome/firefox/başka/normal/kendi tarayıcım) istediyse run_command ile \`start "" <url>\` çalıştır. Görseli göremiyorsan metni ocr_read ile oku (source:"browser").\n` +
3635
3686
  `- Bağımsız araç çağrılarını aynı turda PARALEL ver.\n` +
@@ -3708,6 +3759,11 @@ class Engine {
3708
3759
  ) {
3709
3760
  toolsList = [...toolsList, AGENT_DM_DEF];
3710
3761
  }
3762
+ /* TOOL İSTEĞİ (cephane): tool botu hariç TÜM oturumlar — müşteri bot
3763
+ sohbetleri, paralel ajanlar ve finance ajanları dahil */
3764
+ if (session && !session.isBotDm && session.botId !== 'tool') {
3765
+ toolsList = [...toolsList, TOOL_REQUEST_DEF];
3766
+ }
3711
3767
  /* panel_run yalnız SANDBOX oturumlarında görünsün — diğer panellerde
3712
3768
  modelin alet çantasında olmasın (hook olmadan çalışmaz) */
3713
3769
  if (!(session && session.sbSandbox)) {
@@ -4272,7 +4328,7 @@ class Engine {
4272
4328
  const imgMsg = {
4273
4329
  role: 'user',
4274
4330
  content: [
4275
- { type: 'text', text: '[tarayıcı ekran görüntüsü — bu kareyi analiz et ve devam et]' },
4331
+ { type: 'text', text: '[araç görseli — bu kareyi analiz et ve devam et]' },
4276
4332
  { type: 'image_url', image_url: { url: injectedImage } },
4277
4333
  ],
4278
4334
  };
@@ -4613,33 +4669,21 @@ const skills = require('./skills');
4613
4669
  Admin bot, 5 haneli kodla başka bota özel mesaj atar; hedef botun cevabı
4614
4670
  senkron döner. İzolasyon: DM turları gizli pair oturumunda yürür, bot_dm
4615
4671
  DM oturumlarında KAPALI (döngü koruması). Tüm trafik admin DM Log'ta. */
4616
- async _botDm(args, sessionId, signal) {
4617
- const bots = require('./bots');
4618
- const code = String((args && args.to) || '').replace(/\D/g, '');
4619
- const message = String((args && args.message) || '').slice(0, 6000);
4620
- if (!/^\d{5}$/.test(code)) return { ok: false, error: 'geçersiz kod — 5 haneli bot kodu gir' };
4621
- if (!message.trim()) return { ok: false, error: 'mesaj boş' };
4622
-
4623
- const session = this.cache.get(String(sessionId)) || this._load(String(sessionId));
4624
- const senderBotId = (session && session.botId) || 'beast';
4625
- const senderBot = (typeof this.resolveBot === 'function' && senderBotId) ? this.resolveBot(senderBotId) : null;
4626
- const senderIsAdmin = !session.botId || !!(senderBot && senderBot.admin);
4627
- if (!senderIsAdmin) {
4628
- return { ok: false, error: 'yetki yok — botlar arası DM yalnız yönetici (admin) bot tarafından açılabilir' };
4629
- }
4630
-
4631
- const target = bots.byCode(code);
4632
- if (!target) return { ok: false, error: `bu kotta bot yok: ${code}` };
4633
- if (target.id === senderBotId) return { ok: false, error: 'kendine DM atılamaz' };
4634
4672
 
4635
- /* deterministik pair oturumu: dm + (küçük kod + büyük kod) — aynı çift aynı oturum */
4636
- const codes = [String(senderBot ? senderBot.code || '' : ''), String(target.code || '')]
4673
+ /* Deterministik pair oturumu: dm + (küçük kod + büyük kod) — aynı çift aynı oturum */
4674
+ _botPairId(senderBotId, target) {
4675
+ const sender = (typeof this.resolveBot === 'function' && senderBotId) ? this.resolveBot(senderBotId) : null;
4676
+ const codes = [String(sender ? sender.code || '' : ''), String(target && target.code || '')]
4637
4677
  .map((c) => (/^\d{5}$/.test(c) ? c : '00000'))
4638
4678
  .sort();
4639
- const pairId = 'dm' + codes[0] + codes[1];
4679
+ return 'dm' + codes[0] + codes[1];
4680
+ }
4640
4681
 
4682
+ /* Ortak bot sohbeti: hedef bota mesajı düşür, tur bitene kadar bekle, son
4683
+ asistan cevabını döndür. bot_dm (admin) + tool_request (tool botu) kullanır. */
4684
+ async _botConverse(senderBotId, target, message, signal, timeoutMs) {
4685
+ const pairId = this._botPairId(senderBotId, target);
4641
4686
  let pair = this.cache.get(pairId) || this._load(pairId);
4642
- const fresh = !pair.isBotDm && !pair.messages.length;
4643
4687
  if (!pair.isBotDm) {
4644
4688
  try {
4645
4689
  fs.appendFileSync(this._file(pairId), JSON.stringify({ t: 'botdm', a: senderBotId, b: target.id, at: nowIso() }) + '\n');
@@ -4651,24 +4695,24 @@ const skills = require('./skills');
4651
4695
  /* hedef botun kimliğiyle çalışsın; izolasyon _botSystemBlock'tan gelir */
4652
4696
  this.setSessionBot(pairId, target.id);
4653
4697
  this.setSessionPerm(pairId, target.perm || 'all');
4654
-
4698
+ const senderBot = (typeof this.resolveBot === 'function' && senderBotId) ? this.resolveBot(senderBotId) : null;
4655
4699
  const senderName = senderBot ? senderBot.name : 'Beast';
4656
- const senderCode = senderBot && /^\d{5}$/.test(String(senderBot.code || '')) ? senderBot.code : codes[0];
4657
- const prefix = `[BOT DM — gönderen bot: ${senderName} (kod ${senderCode}) — cevabını kısa ve net ver, araç kullanman gerekmiyorsa kullanma]`;
4700
+ const senderCode = senderBot && /^\d{5}$/.test(String(senderBot.code || '')) ? senderBot.code : '';
4701
+ const prefix = `[BOT DM — gönderen bot: ${senderName}${senderCode ? ' (kod ' + senderCode + ')' : ''} — cevabını kısa ve net ver, araç kullanman gerekmiyorsa kullanma]`;
4658
4702
  const before = pair.messages.length;
4659
4703
  const sent = this.send(pairId, { text: prefix + '\n' + message });
4660
4704
  if (!sent) return { ok: false, error: 'DM oturumu başlatılamadı' };
4661
-
4662
- /* tur bitene kadar bekle (ctrl kaydı düşer), en fazla 120 sn */
4705
+ /* tur bitene kadar bekle (ctrl kaydı düşer) */
4706
+ const limit = Math.max(30000, Math.min(600000, Number(timeoutMs) || 120000));
4663
4707
  const t0 = Date.now();
4664
- while (Date.now() - t0 < 120000) {
4708
+ while (Date.now() - t0 < limit) {
4665
4709
  if (signal && signal.aborted) return { ok: false, error: 'iptal edildi' };
4666
4710
  await new Promise((r) => setTimeout(r, 400));
4667
4711
  if (!this.ctrls.has(pairId)) break;
4668
4712
  }
4669
4713
  if (this.ctrls.has(pairId)) {
4670
- try { this.interrupt(pairId, 'bot DM zaman aşımına uğradı (120 sn) — tur kesildi'); } catch {}
4671
- return { ok: false, error: 'hedef bot zaman aşımına uğradı (120 sn)' };
4714
+ try { this.interrupt(pairId, 'bot DM zaman aşımına uğradı — tur kesildi'); } catch {}
4715
+ return { ok: false, error: `hedef bot zaman aşımına uğradı (${Math.round(limit / 1000)} sn) — TOOLS panelinden sonucu takip et` };
4672
4716
  }
4673
4717
  const after = this.cache.get(pairId) || pair;
4674
4718
  const newMsgs = after.messages.slice(before);
@@ -4678,6 +4722,74 @@ const skills = require('./skills');
4678
4722
  return { ok: true, from: target.name, code: target.code, reply };
4679
4723
  }
4680
4724
 
4725
+ async _botDm(args, sessionId, signal) {
4726
+ const bots = require('./bots');
4727
+ const code = String((args && args.to) || '').replace(/\D/g, '');
4728
+ const message = String((args && args.message) || '').slice(0, 6000);
4729
+ if (!/^\d{5}$/.test(code)) return { ok: false, error: 'geçersiz kod — 5 haneli bot kodu gir' };
4730
+ if (!message.trim()) return { ok: false, error: 'mesaj boş' };
4731
+
4732
+ const session = this.cache.get(String(sessionId)) || this._load(String(sessionId));
4733
+ const senderBotId = (session && session.botId) || 'beast';
4734
+ const senderBot = (typeof this.resolveBot === 'function' && senderBotId) ? this.resolveBot(senderBotId) : null;
4735
+ const senderIsAdmin = !session.botId || !!(senderBot && senderBot.admin);
4736
+ if (!senderIsAdmin) {
4737
+ return { ok: false, error: 'yetki yok — botlar arası DM yalnız yönetici (admin) bot tarafından açılabilir' };
4738
+ }
4739
+
4740
+ const target = bots.byCode(code);
4741
+ if (!target) return { ok: false, error: `bu kotta bot yok: ${code}` };
4742
+ if (target.id === senderBotId) return { ok: false, error: 'kendine DM atılamaz' };
4743
+ return this._botConverse(senderBotId, target, message, signal, 120000);
4744
+ }
4745
+
4746
+ /* TOOL İSTEĞİ (cephane): her oturum/bot eksik aracı TOOL botuna yazdırır.
4747
+ Admin şartı YOK — amaç tüm ajanların tool üretebilmesi; tool botunun
4748
+ kendisi ve DM oturumları hariç (döngü koruması). */
4749
+ async _toolRequest(args, sessionId, signal) {
4750
+ const bots = require('./bots');
4751
+ const task = String((args && args.task) || '').slice(0, 4000);
4752
+ const context = String((args && args.context) || '').slice(0, 4000);
4753
+ if (!task.trim()) return { ok: false, error: 'task gerekli — hangi araç, ne yapacak, girdi/çıktısı ne?' };
4754
+ const session = this.cache.get(String(sessionId)) || this._load(String(sessionId));
4755
+ if (session && session.isBotDm) return { ok: false, error: 'DM oturumunda tool_request kullanılamaz' };
4756
+ const senderBotId = (session && session.botId) || 'beast';
4757
+ if (senderBotId === 'tool') return { ok: false, error: 'Tool botu kendi işini kendisi yapar — doğrudan yaz' };
4758
+ const all = bots.list() || [];
4759
+ const target = all.find((b) => b && b.id === 'tool') || all.find((b) => b && /tool/i.test(String(b.name || '')));
4760
+ if (!target) return { ok: false, error: 'Tool botu bulunamadı — botlar sayfasından oluştur' };
4761
+ const who = (() => {
4762
+ try {
4763
+ const b = typeof this.resolveBot === 'function' ? this.resolveBot(senderBotId) : null;
4764
+ return (b && b.name) || (session && session.bgTitle) || 'Beast';
4765
+ } catch { return 'Beast'; }
4766
+ })();
4767
+ const message =
4768
+ `TOOL İSTEĞİ (gönderen: ${who}):\n` + task + '\n' +
4769
+ (context ? `\nBAĞLAM (kod/yol/örnek veri/endpoint):\n` + context + '\n' : '') +
4770
+ `\nHatırlatma: skill("tool-yazma") oku; %APPDATA%\\beast\\tools\\<slug>\\ içine tool.json + run.js yaz; run_command ile TEST DÖNGÜSÜNÜ işlet (çıktı {ok:true} olana kadar hata oku → edit_file ile düzelt → yeniden test; en fazla 5 deneme). Ancak test geçince yayınla; geçmeyen aracı yayınlama. Raporunda adımları yaz: yazıldı → test sonucu → (varsa) düzeltmeler → YAYINDA/BAŞARISIZ + tek satır çağrı örneği.`;
4771
+ const r = await this._botConverse(senderBotId, target, message, signal, 240000);
4772
+ /* RAPOR: tool yazım/doğrulama adımları bağlı entegrasyonlara (WA/TG/Discord) düşer */
4773
+ const notifyIntegrations = (text) => {
4774
+ try { if (typeof this.integrationNotify === 'function') this.integrationNotify(text); } catch {}
4775
+ };
4776
+ if (!r || !r.ok) {
4777
+ notifyIntegrations('🛠️ Tool botu BAŞARISIZ — isteyen: ' + who + '\nİstenen: ' + task.slice(0, 140) + '\nSebep: ' + String((r && r.error) || 'yanıt yok'));
4778
+ return r || { ok: false, error: 'Tool botu yanıt vermedi' };
4779
+ }
4780
+ notifyIntegrations(
4781
+ '🛠️ Tool botu raporu — isteyen: ' + who + '\n' +
4782
+ 'İstenen: ' + task.slice(0, 140) + '\n' +
4783
+ String(r.reply || '').replace(/\s+/g, ' ').slice(0, 500)
4784
+ );
4785
+ return {
4786
+ ok: true,
4787
+ tool_bot: r.from,
4788
+ reply: r.reply,
4789
+ note: 'Araç hazır olduğunda modele tool__<slug> olarak ANINDA açılır (doğrulama testini Tool botu yaptı; adımlar entegrasyonlara bildirildi).',
4790
+ };
4791
+ }
4792
+
4681
4793
  /* ADMIN İZLEME: tüm botlar arası DM oturumlarının listesi */
4682
4794
  listBotDmSessions() {
4683
4795
  const bots = require('./bots');
@@ -5046,7 +5158,7 @@ const skills = require('./skills');
5046
5158
  }
5047
5159
  /* AJAN DM: ajanlar arası mesaj — hedef koşan ajanın oturumuna düşer */
5048
5160
  if (name === 'agent_dm') {
5049
- return JSON.stringify(this._agentDmSend(sessionId, args));
5161
+ return JSON.stringify(await this._agentDmSend(sessionId, args));
5050
5162
  }
5051
5163
  /* KİŞİSEL TOOL: %APPDATA%\beast\tools\ — çocuk node prosesinde izole koşar */
5052
5164
  if (String(name).startsWith('tool__')) {
@@ -5236,6 +5348,11 @@ const skills = require('./skills');
5236
5348
  const r = await this._botDm(args, sessionId, signal);
5237
5349
  return JSON.stringify(r);
5238
5350
  }
5351
+ /* TOOL İSTEĞİ (cephane): eksik/bozuk aracı TOOL botuna yazdırır —
5352
+ tanım toolsList'e ekleniyordu ama dispatch'e bağlı değildi, burada bağlanır */
5353
+ if (name === 'tool_request') {
5354
+ return JSON.stringify(await this._toolRequest(args || {}, sessionId, signal));
5355
+ }
5239
5356
  if (name === 'send_file') {
5240
5357
  if (typeof this.fileSend !== 'function') {
5241
5358
  return JSON.stringify({ ok: false, error: 'dosya gönderim köprüsü yok' });
@@ -5610,7 +5727,7 @@ const AGENT_DM_DEF = {
5610
5727
  function: {
5611
5728
  name: 'agent_dm',
5612
5729
  description:
5613
- 'Send a short DM to another running agent (parallel agents / finance agents) to coordinate: share findings, ask status, warn about risk, hand off work, and MAKE JOINT DECISIONS. `to` = target session id OR a keyword from the agent title (e.g. "GOLD", "Trader"). `topic` = short subject label — replies to the same topic stay in the SAME conversation thread, so ALWAYS reuse the topic you were DMed with when replying. `group` = optional group-chat name (e.g. "GOLD EKIP"): creates or reuses a group conversation, adds the target agent as a member, and your message is delivered to EVERY member — use groups when a decision needs multiple agents.',
5730
+ 'Send a short DM to another running agent (parallel agents / finance agents) to coordinate: share findings, ask status, warn about risk, hand off work, and MAKE JOINT DECISIONS. `to` = target session id OR a keyword from the agent title (e.g. "GOLD", "Trader"). `topic` = short subject label — replies to the same topic stay in the SAME conversation thread, so ALWAYS reuse the topic you were DMed with when replying. `group` = optional group-chat name (e.g. "GOLD EKIP"): creates or reuses a group conversation, adds the target agent as a member, and your message is delivered to EVERY member — use groups when a decision needs multiple agents. `image` shares a chart/screen as an attachment the receiving agent REALLY sees and the AJAN DM panel shows: true = your last screenshot (fresh capture if none); a file path (e.g. the tool__mt5_shot "path") or a data URL = that exact image.',
5614
5731
  parameters: {
5615
5732
  type: 'object',
5616
5733
  properties: {
@@ -5618,12 +5735,32 @@ const AGENT_DM_DEF = {
5618
5735
  topic: { type: 'string', description: 'Short subject label for the conversation thread, e.g. "GOLD pozisyon riski"' },
5619
5736
  message: { type: 'string', description: 'Short message (1-3 sentences)' },
5620
5737
  group: { type: 'string', description: 'Optional group-chat name — send to ALL members of that group (creates it on first use, adds the target agent)' },
5738
+ image: { type: ['boolean', 'string'], description: 'true = son ekran görüntün (yoksa taze yakalar); dosya yolu (ör. tool__mt5_shot dönen PNG path) ya da data:image URL = o görsel iliştirilir. Panelde ve karşı ajanda GÖRÜNÜR; grafik/ekran bulgusunu paylaşmak için kullan' },
5621
5739
  },
5622
5740
  required: ['message'],
5623
5741
  },
5624
5742
  },
5625
5743
  };
5626
5744
 
5745
+ /* TOOL İSTEĞİ (cephane kuralı): eksik/bozuk araç TOOL botuna yazdırılır.
5746
+ Tool botunun kendisi ve bot-DM oturumları hariç TÜM oturumlara açılır. */
5747
+ const TOOL_REQUEST_DEF = {
5748
+ type: 'function',
5749
+ function: {
5750
+ name: 'tool_request',
5751
+ description:
5752
+ 'İhtiyaç duyduğun bir araç eksikse ya da bozuksa TOOL botuna yazdırır ve doğrulanmış aracın raporunu döndürür. Tool yazımı Tool botunun TEK işidir — sen tool yazmaya çalışma. task: araç adı + ne yapacak + girdi/çıktı sözleşmesi; context: örnek veri, dosya yolu, endpoint veya hata çıktısı. Araç hazır olunca tool__<ad> olarak ANINDA çağrılabilir.',
5753
+ parameters: {
5754
+ type: 'object',
5755
+ properties: {
5756
+ task: { type: 'string', description: 'Araç ihtiyacı: ad, işlev, girdi/çıktı sözleşmesi' },
5757
+ context: { type: 'string', description: 'Örnek veri, yol, endpoint ya da hata çıktısı' },
5758
+ },
5759
+ required: ['task'],
5760
+ },
5761
+ },
5762
+ };
5763
+
5627
5764
  const TOOLS = [
5628
5765
  ...tools.definitions,
5629
5766
  {
@@ -6409,11 +6546,22 @@ Engine.prototype.clearTodos = function (sid) {
6409
6546
  /* Ajan DM gönder: hedef koşan paralel/finance ajanı (id YA DA başlık
6410
6547
  anahtar kelimesi). Teslim _pendingReports kuyruğuyla yapılır — hedef
6411
6548
  meşgulse mesaj done olunca düşer, kaybolmaz. Panel agent-dms.json'dan. */
6412
- Engine.prototype._agentDmSend = function (fromSid, args) {
6549
+ Engine.prototype._agentDmSend = async function (fromSid, args) {
6413
6550
  try {
6414
6551
  const to = String((args && args.to) || '').trim();
6415
6552
  const text = String((args && args.message) || '').trim().slice(0, 1200);
6416
6553
  if (!text) return { ok: false, error: 'mesaj boş' };
6554
+ /* GÖRSEL DM: image:true → son ekran görüntüsü (yoksa taze yakalama);
6555
+ image:"<dosya yolu>" ya da data URL → o görsel BİREBİR iliştirilir
6556
+ (ör. tool__mt5_shot dönen PNG path'i). Karşı ajan görüntüyü görür. */
6557
+ const imgArg = args && args.image;
6558
+ let image = '';
6559
+ if (typeof imgArg === 'string' && imgArg.trim()) {
6560
+ const s = imgArg.trim();
6561
+ image = /^data:image\//i.test(s) ? s : this._agentDmImageFromFile(s);
6562
+ } else if (imgArg === true || imgArg === 'true') {
6563
+ image = await this._agentDmCaptureImage(fromSid);
6564
+ }
6417
6565
  const groupName = String((args && args.group) || '').replace(/\s+/g, ' ').trim().slice(0, 40);
6418
6566
  const jobs = this._bgJobs || new Map();
6419
6567
  const fromJob = jobs.get(String(fromSid));
@@ -6538,6 +6686,7 @@ Engine.prototype._agentDmSend = function (fromSid, args) {
6538
6686
  groupTitle: g.title,
6539
6687
  topic,
6540
6688
  text,
6689
+ ...(image ? { image } : {}),
6541
6690
  };
6542
6691
  this._pushAgentDm(dm);
6543
6692
  /* canlı event TEK SEFER — üye başına emit edilirse panelde çoğalır */
@@ -6549,6 +6698,7 @@ Engine.prototype._agentDmSend = function (fromSid, args) {
6549
6698
  text:
6550
6699
  `[AJAN DM (grup: "${g.title}") — ${dm.fromTitle} · konu: "${topic}"]\n${text}\n` +
6551
6700
  `(Cevap yalnızca aksiyon/karar GEREKİYORSA ver — agent_dm group: "${g.title}", to: "${dm.fromTitle}", topic: "${topic}"; teşekkür/onay yazma.)`,
6701
+ ...(image ? { image } : {}),
6552
6702
  });
6553
6703
  this.flushPendingReports(m);
6554
6704
  }
@@ -6564,7 +6714,7 @@ Engine.prototype._agentDmSend = function (fromSid, args) {
6564
6714
 
6565
6715
  /* BİREYSEL DM — DÜZ CHAT OTURUMUNA (sahibin sohbet geçmişi) ENJEKTE
6566
6716
  ETMEYİZ: DM trafik yalnız AJAN DM konsolunda görünür. */
6567
- const dm = { at, from: String(fromSid), fromTitle, to: String(target), toTitle, topic, text };
6717
+ const dm = { at, from: String(fromSid), fromTitle, to: String(target), toTitle, topic, text, ...(image ? { image } : {}) };
6568
6718
  this._pushAgentDm(dm);
6569
6719
  this._persistAgentDms();
6570
6720
  emitSafe(this, target, { type: 'agent-dm', ...dm });
@@ -6574,6 +6724,7 @@ Engine.prototype._agentDmSend = function (fromSid, args) {
6574
6724
  text:
6575
6725
  `[AJAN DM — ${dm.fromTitle} · konu: "${topic}"]\n${text}\n` +
6576
6726
  `(Cevap yalnızca aksiyon/karar GEREKİYORSA ver — agent_dm to: "${dm.fromTitle}", topic: "${topic}"; teşekkür/onay yazma.)`,
6727
+ ...(image ? { image } : {}),
6577
6728
  });
6578
6729
  this.flushPendingReports(target);
6579
6730
  }
@@ -6583,6 +6734,58 @@ Engine.prototype._agentDmSend = function (fromSid, args) {
6583
6734
  }
6584
6735
  };
6585
6736
 
6737
+ /* AJAN DM görseli (dosyadan): png/jpg/webp/gif dosyasını data URL'e çevirir.
6738
+ Send_file kapısına takılmadan (ör. tool__mt5_shot path'i) görsel paylaşımı;
6739
+ 2.5MB üstü DM kaydını şişirmesin diye atlanır. */
6740
+ Engine.prototype._agentDmImageFromFile = function (p) {
6741
+ try {
6742
+ const fp = String(p || '').trim();
6743
+ if (!fp) return '';
6744
+ const ext = path.extname(fp).toLowerCase();
6745
+ const mime =
6746
+ ext === '.png' ? 'image/png'
6747
+ : ext === '.jpg' || ext === '.jpeg' ? 'image/jpeg'
6748
+ : ext === '.webp' ? 'image/webp'
6749
+ : ext === '.gif' ? 'image/gif'
6750
+ : '';
6751
+ if (!mime) return '';
6752
+ const buf = fs.readFileSync(fp);
6753
+ if (!buf.length || buf.length > 2500000) return '';
6754
+ return 'data:' + mime + ';base64,' + buf.toString('base64');
6755
+ } catch {
6756
+ return '';
6757
+ }
6758
+ };
6759
+
6760
+ /* AJAN DM görseli: gönderenin oturumundaki EN SON ekran görüntüsünü (yoksa
6761
+ taze yakalama) data URL olarak döndürür. Çok büyük görseller DM kaydını
6762
+ şişirmesin diye 2.5MB üstü atlanır. */
6763
+ Engine.prototype._agentDmCaptureImage = async function (sid) {
6764
+ let found = '';
6765
+ const sess = this.cache.get(String(sid || '')) || null;
6766
+ const msgs = (sess && Array.isArray(sess.messages) ? sess.messages : []);
6767
+ for (let i = msgs.length - 1; i >= 0 && !found; i--) {
6768
+ const c = msgs[i] && msgs[i].content;
6769
+ if (!Array.isArray(c)) continue;
6770
+ for (let j = c.length - 1; j >= 0; j--) {
6771
+ const p = c[j];
6772
+ if (p && p.type === 'image_url' && p.image_url && /^data:image\//i.test(String(p.image_url.url || ''))) {
6773
+ found = String(p.image_url.url);
6774
+ break;
6775
+ }
6776
+ }
6777
+ }
6778
+ if (!found && this.computer && typeof this.computer.look === 'function') {
6779
+ try {
6780
+ const shot = await this.computer.look();
6781
+ const url = typeof shot === 'string' ? shot : (shot && (shot.image || shot.dataUrl || shot.__injectImage)) || '';
6782
+ if (/^data:image\//i.test(String(url || ''))) found = String(url);
6783
+ } catch {}
6784
+ }
6785
+ if (!found || found.length > 2500000) return '';
6786
+ return found;
6787
+ };
6788
+
6586
6789
  /* İki ajanın ORTAK üyesi olduğu AÇIK grup var mı — 1:1 DM'leri grup içine
6587
6790
  yönlendirirken kullanılır (ekip içi trafik tek thread'de toplanır). */
6588
6791
  Engine.prototype._agentDmSharedGroup = function (a, b) {
@@ -6735,7 +6938,16 @@ Engine.prototype._persistAgentDms = function () {
6735
6938
  try {
6736
6939
  const groups = {};
6737
6940
  for (const [, g] of this._agentGroups || new Map()) groups[g.id] = g;
6738
- this._atomicWrite(this._agentDmsFile, JSON.stringify({ dms: this._agentDms, groups }, null, 2));
6941
+ /* görsel bütçesi: disk şişmesin — en yeni görseller tutulur (~24MB) */
6942
+ const dms = (this._agentDms || []).slice();
6943
+ let budget = 24 * 1024 * 1024;
6944
+ for (let i = dms.length - 1; i >= 0; i--) {
6945
+ const d = dms[i];
6946
+ if (!d || typeof d.image !== 'string' || !d.image) continue;
6947
+ if (d.image.length <= budget) { budget -= d.image.length; continue; }
6948
+ dms[i] = { ...d, image: '', imageOmitted: true };
6949
+ }
6950
+ this._atomicWrite(this._agentDmsFile, JSON.stringify({ dms, groups }, null, 2));
6739
6951
  } catch {}
6740
6952
  };
6741
6953
 
@@ -22,6 +22,7 @@ const NAMES = [
22
22
  'mt5_history',
23
23
  'mt5_alerts',
24
24
  'mt5_note',
25
+ 'mt5_ea',
25
26
  'mt5_trade',
26
27
  'mt5_close',
27
28
  'mt5_modify',
@@ -226,6 +227,29 @@ const definitions = NAMES.map((name) => {
226
227
  required: ['symbol', 'note'],
227
228
  },
228
229
  },
230
+ mt5_ea: {
231
+ description:
232
+ 'BEASTFINANCE EA KÖPRÜSÜ (grafikte çalışan uzman danışman): action:"status" → EA heartbeat (yüklü mü, AutoTrading/EA izni, equity, pozisyon, son ack); "ping" → EA canlı yanıt; "chart" → aktif grafiğin sembol/periyot/fiyat/spread bilgisi; "note" → {symbol?, text, levels:[{price,label}]} grafik panosuna not + yatay seviye çizgileri yazar. NOT: yazdığın pano ve çizgiler görsel ajanın screenshot\'ında GÖRÜNÜR — GERÇEK grafik PNG\'si için varsayılan kurulu tool__mt5_shot kullan (EA "shot" komutu; görsel ajanın gözüne gelir), seviyeleri çizdikten sonra tool__mt5_shot ile görsel doğrula; grafiği agent_dm image ile ekibe paylaş.',
233
+ parameters: {
234
+ type: 'object',
235
+ properties: {
236
+ action: { type: 'string', enum: ['status', 'ping', 'chart', 'note'] },
237
+ symbol: { type: 'string', description: 'note: yalnız bu sembolün grafiğine uygulanır (boş = tüm grafikler)' },
238
+ text: { type: 'string', description: 'note: grafik panosuna yazılacak kısa analiz/plan metni' },
239
+ levels: {
240
+ type: 'array',
241
+ description: 'note: yatay seviye çizgileri (destek/direnç/SL/TP)',
242
+ items: {
243
+ type: 'object',
244
+ properties: { price: { type: 'number' }, label: { type: 'string' } },
245
+ required: ['price'],
246
+ },
247
+ },
248
+ timeoutSec: { type: 'number', description: 'ping/chart: EA yanıt bekleme süresi sn (varsayılan 8)' },
249
+ },
250
+ required: ['action'],
251
+ },
252
+ },
229
253
  mt5_trade: {
230
254
  description:
231
255
  'PİYASA EMRİ AÇAR: mt5_trade {symbol, side:"buy"|"sell", volume, sl?, tp?, comment?, reason?}. Lot limiti ve max pozisyon sayısı sistem tarafından zorlanır. Otomatik işlem anahtarı kapalıysa reddedilir. SL/TP vermek ŞIDDETLİ önerilir. reason: kararın tek cümlelik tezi (günlüğe yazılır, performans değerlendirmesinde kullanılır).',
@@ -410,6 +434,34 @@ const handlers = {
410
434
  }, ctx || {});
411
435
  return { ok: true, saved: true };
412
436
  },
437
+ async mt5_ea(args) {
438
+ if (!mt5.running) return notConnected();
439
+ const action = String(args.action || 'status').toLowerCase();
440
+ if (action === 'status') {
441
+ const data = await bcall('ea_status', {}, 10000);
442
+ return { ok: true, ...data };
443
+ }
444
+ if (action === 'note') {
445
+ const levels = Array.isArray(args.levels)
446
+ ? args.levels
447
+ .slice(0, 20)
448
+ .map((x) => ({ price: Number(x && x.price) || 0, label: String((x && x.label) || '').slice(0, 40) }))
449
+ .filter((x) => x.price > 0)
450
+ : [];
451
+ const data = await bcall('ea_note', {
452
+ symbol: String(args.symbol || '').trim().toUpperCase(),
453
+ text: String(args.text || '').slice(0, 4000),
454
+ levels,
455
+ }, 10000);
456
+ return { ok: true, ...data, note: 'Grafik panosu güncellendi — computer_look screenshot\'ında görünür' };
457
+ }
458
+ if (action === 'ping' || action === 'chart') {
459
+ const timeoutSec = Math.max(2, Math.min(20, Number(args.timeoutSec) || 8));
460
+ const data = await bcall('ea_cmd', { cmd: action, timeoutSec }, (timeoutSec + 6) * 1000);
461
+ return { ok: true, ...data };
462
+ }
463
+ return { ok: false, error: 'action: status|ping|chart|note' };
464
+ },
413
465
  async mt5_risksize(args) {
414
466
  if (!mt5.running) return notConnected();
415
467
  const cfg = getCfg();