beast-agent 2.39.0 → 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 (37) hide show
  1. package/package.json +1 -1
  2. package/src/agent/bots.js +2 -2
  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 +51 -16
  30. package/src/agent/financetools.js +1 -1
  31. package/src/agent/mt5/BeastFinance.mq5 +140 -3
  32. package/src/agent/mt5/BeastShot.tpl +0 -0
  33. package/src/agent/mt5setup.js +22 -0
  34. package/src/agent/seeds/mql5/SKILL.md +4 -2
  35. package/src/main.js +1 -1
  36. package/src/renderer/renderer.js +11 -1
  37. package/src/renderer/style.css +15 -0
@@ -0,0 +1,92 @@
1
+ 'use strict';
2
+ // MT5 SL/TP GUNCELLE — stdin: JSON args · stdout: JSON sonuç
3
+ const { spawn } = require('child_process');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+
7
+ /* TAŞINABİLİR YOL ÇÖZÜMÜ: Beast gömülü python → sistem python'ları → PATH.
8
+ Köprü dosyası %APPDATA%\beast\finance\mt5_bridge.py'den (veya BEAST_FINANCE_DIR)
9
+ okunur; ortam değişkenleriyle geçersiz kılınabilir. */
10
+ function resolvePython() {
11
+ const cands = [];
12
+ if (process.env.BEAST_PYTHON) cands.push({ exe: String(process.env.BEAST_PYTHON), args: [] });
13
+ if (process.env.APPDATA) cands.push({ exe: path.join(process.env.APPDATA, 'beast', 'py', 'python.exe'), args: [] });
14
+ for (const v of ['314', '313', '312', '311', '310']) cands.push({ exe: 'C:\\Python' + v + '\\python.exe', args: [] });
15
+ cands.push({ exe: 'python', args: [] }, { exe: 'py', args: ['-3'] });
16
+ for (const c of cands) {
17
+ if (c.exe === 'python' || c.exe === 'py') return c;
18
+ try { if (fs.existsSync(c.exe)) return c; } catch (e) {}
19
+ }
20
+ return { exe: 'python', args: [] };
21
+ }
22
+ function resolveBridge() {
23
+ const cands = [];
24
+ if (process.env.BEAST_BRIDGE) cands.push(String(process.env.BEAST_BRIDGE));
25
+ if (process.env.BEAST_FINANCE_DIR) cands.push(path.join(String(process.env.BEAST_FINANCE_DIR), 'mt5_bridge.py'));
26
+ if (process.env.APPDATA) cands.push(path.join(process.env.APPDATA, 'beast', 'finance', 'mt5_bridge.py'));
27
+ for (const p of cands) { try { if (p && fs.existsSync(p)) return p; } catch (e) {} }
28
+ return cands[0] || '';
29
+ }
30
+ const PYSPEC = resolvePython();
31
+ const PY = PYSPEC.exe;
32
+ const PY_ARGS = PYSPEC.args;
33
+ const BRIDGE = resolveBridge();
34
+
35
+ const TIMEOUT = 35000;
36
+
37
+ function call(requests) {
38
+ return new Promise((resolve) => {
39
+ let py;
40
+ try { py = spawn(PY, [...PY_ARGS, BRIDGE], { windowsHide: true, stdio: ['pipe', 'pipe', 'pipe'] }); }
41
+ catch (e) { return resolve({ ok: false, error: 'python baslatilamadi: ' + e.message }); }
42
+ const results = {};
43
+ let buf = '';
44
+ let errTail = '';
45
+ let settled = false;
46
+ const finish = (obj) => { if (!settled) { settled = true; clearTimeout(timer); try { py.kill(); } catch (e) {} resolve(obj); } };
47
+ const timer = setTimeout(() => finish({ ok: false, error: 'zaman asimi: MT5 terminali acik ve yanit veriyor mu?' }), TIMEOUT);
48
+ py.stdout.on('data', (d) => {
49
+ buf += d.toString('utf8');
50
+ let i;
51
+ while ((i = buf.indexOf('\n')) >= 0) {
52
+ const line = buf.slice(0, i).trim(); buf = buf.slice(i + 1);
53
+ if (!line) continue;
54
+ let o = null;
55
+ try { o = JSON.parse(line); } catch (e) { continue; }
56
+ if (o && o.event) continue;
57
+ if (o && typeof o.id === 'number' && Object.prototype.hasOwnProperty.call(o, 'ok')) {
58
+ results[o.id] = o;
59
+ if (Object.keys(results).length >= requests.length) {
60
+ const arr = requests.map((_, k) => results[k + 1]);
61
+ finish(arr.length === 1 ? arr[0] : { ok: true, results: arr });
62
+ }
63
+ }
64
+ }
65
+ });
66
+ py.stderr.on('data', (d) => { errTail = (errTail + d.toString('utf8')).slice(-800); });
67
+ py.on('error', (e) => finish({ ok: false, error: 'python baslatilamadi: ' + e.message }));
68
+ py.on('close', (code) => { if (Object.keys(results).length < requests.length) finish({ ok: false, error: 'bridge erken cikti code=' + code + ' ' + errTail }); });
69
+ requests.forEach((r, k) => py.stdin.write(JSON.stringify({ id: k + 1, method: r.method, params: r.params || {} }) + '\n'));
70
+ py.stdin.end();
71
+ });
72
+ }
73
+
74
+ (async function main() {
75
+ let args = {};
76
+ try { args = JSON.parse(fs.readFileSync(0, 'utf8') || '{}'); } catch (e) {}
77
+ const ticket = Number(args.ticket || 0);
78
+ if (!ticket) return console.log(JSON.stringify({ ok: false, error: 'ticket zorunlu' }));
79
+ if (args.sl === undefined && args.tp === undefined) return console.log(JSON.stringify({ ok: false, error: 'sl veya tp vermelisin' }));
80
+ const params = { ticket: ticket, sl: Number(args.sl || 0), tp: Number(args.tp || 0) };
81
+ const r = await call([{ method: 'modify', params: params }]);
82
+ if (r.ok && r.data && r.data.result) {
83
+ const res = r.data.result;
84
+ console.log(JSON.stringify({ ok: true, retcode: res.retcode, comment: res.comment, detay: r.data }));
85
+ } else {
86
+ console.log(JSON.stringify({ ok: false, error: r.error || 'SL/TP guncelleme reddedildi' }));
87
+ }
88
+ })();
89
+
90
+ /* PATCH 2026-09-10b: electron app-mode cocuk prosesi kapanmiyordu (90sn timeout).
91
+ stdout'un boruya bosalmasi icin kisa bekleme, sonra zorla cik. */
92
+ setTimeout(() => process.exit(0), 1500);
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "mt5_sltp",
3
+ "description": "Açık pozisyonun STOP LOSS / TAKE PROFIT seviyelerini günceller: ticket zorunlu, sl ve/veya tp verilir (0 = o seviyeyi kaldir). SL'siz pozisyon bırakmamak için risk yönetiminde kullanılır.",
4
+ "parameters": {
5
+ "type": "object",
6
+ "properties": {
7
+ "ticket": { "type": "number", "description": "Pozisyonun ticket numarasi" },
8
+ "sl": { "type": "number", "description": "Yeni stop loss fiyati (0 = kaldir)" },
9
+ "tp": { "type": "number", "description": "Yeni take profit fiyati (0 = kaldir)" }
10
+ },
11
+ "required": ["ticket"]
12
+ },
13
+ "updatedAt": "2026-09-09T18:48:00.000Z"
14
+ }
@@ -402,6 +402,7 @@ class Engine {
402
402
  if (nrBoot.unref) nrBoot.unref();
403
403
  skills.seedIfEmpty();
404
404
  agentdefs.seedIfEmpty();
405
+ try { customtools.seedDefaults(); } catch {} // varsayılan MT5 toolları (mt5_shot, mt5_barlar2, ...) — tüm oturumlara açılır
405
406
  }
406
407
 
407
408
  /* ---------- opencode agent.ts port: özel ajanlar ---------- */
@@ -1918,11 +1919,11 @@ class Engine {
1918
1919
  risk:
1919
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ı).',
1920
1921
  technic:
1921
- '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.',
1922
1923
  macro:
1923
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.',
1924
1925
  visual:
1925
- '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). Her turda computer_look ile MT5 grafiğini yakala (gerekirse computer_act ile timeframe/zoom ayarla); web grafikleri için browser_open + browser_screenshot, 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örseliyle birlikte agent_dm ile ANA TRADER\'a ya da ekibe bildir (yakaladığın grafiği paylaş).',
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.',
1926
1927
  })[String((session && session.financeRole) || '')] || '';
1927
1928
  /* ROL → SKILL eşleştirmesi (ayarlar modalı): rol başına TEK ve ZORUNLU
1928
1929
  skill; ANA TRADER için ayrıca PLAYBOOK skill'i (roleSkills.trader).
@@ -1962,19 +1963,22 @@ class Engine {
1962
1963
  (roleBlockFull ? roleBlockFull + '\n' : '') +
1963
1964
  (teamLine || '') +
1964
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' +
1965
- '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; yazdığın not ve çizgiler computer_look screenshot\'ında görünür, entegrasyon bildirimleriyle birlikte çalışır.\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' +
1966
1968
  'YETKİLERİN (AÇIK — çekinmeden kullan):\n' +
1967
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' +
1968
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' +
1969
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' +
1970
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' +
1971
- 'GRAFİK & EKRAN GÖRÜNTÜSÜ (karar öncesi görsel doğrulama):\n' +
1972
- '- computer_look: masaüstü ekranını yakalar — MT5 grafiği SONRAKİ turda görsel olarak önüne gelir; trend/formasyon/mum yapısı + SL/TP çizgilerini doğrudan gör. Önemli işlem kararından önce grafiği gözle doğrula.\n' +
1973
- '- mt5_ea action:"note": grafiğe kısa analiz/plan metni + seviye çizgileri (destek/direnç/SL/TP) yazar — BeastFinance EA panosu computer_look screenshot\'ında GÖRÜNÜR; entegrasyon bildirimleriyle birlikte kullan.\n' +
1974
- '- browser_screenshot: web tabanlı grafikler (TradingView, Investing, MQL5) — browser_open ile aç, ekran görüntüsünü incele.\n' +
1975
- '- Görsel göremiyorsan (metin-model) ocr_read kullan: source:"screen" masaüstünü, source:"browser" paneli OCR ile okur; grafikteki fiyat/seviyeleri metne çevirir.\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' +
1976
1978
  '- Grafikte gezinme/ölçek: computer_act ile timeframe, scroll, zoom (arka planda çalışır — kullanıcının faresini çalmaz).\n' +
1977
- '- EKSİK ARAÇ: özel grafik aracı gerekiyorsa (ör. MT5 grafiğini PNG dosyasına kaydetme, ç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' +
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' +
1978
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' +
1979
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' +
1980
1984
  (symbols ? `İZLEME LİSTESİ: ${symbols}\n` : '') +
@@ -2049,7 +2053,7 @@ class Engine {
2049
2053
  if (bctx) {
2050
2054
  parts.push(
2051
2055
  '# MT5 / BEAST FINANCE ENTEGRASYONU\n' +
2052
- '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) — pano ve çizgiler screenshot\'larda görünür. Elle EA ekleme varsayma; eksik araç gerekiyorsa Tool botuna tool_request at.'
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.'
2053
2057
  );
2054
2058
  }
2055
2059
  /* opencode instruction port (ajan modu): workspace AGENTS/CLAUDE/CONTEXT
@@ -4324,7 +4328,7 @@ class Engine {
4324
4328
  const imgMsg = {
4325
4329
  role: 'user',
4326
4330
  content: [
4327
- { 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]' },
4328
4332
  { type: 'image_url', image_url: { url: injectedImage } },
4329
4333
  ],
4330
4334
  };
@@ -5723,7 +5727,7 @@ const AGENT_DM_DEF = {
5723
5727
  function: {
5724
5728
  name: 'agent_dm',
5725
5729
  description:
5726
- '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:true` attaches your last screenshot (fresh capture if none) — the receiving agent and the AJAN DM panel see it as an actual image; use it to share chart/screen findings.',
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.',
5727
5731
  parameters: {
5728
5732
  type: 'object',
5729
5733
  properties: {
@@ -5731,7 +5735,7 @@ const AGENT_DM_DEF = {
5731
5735
  topic: { type: 'string', description: 'Short subject label for the conversation thread, e.g. "GOLD pozisyon riski"' },
5732
5736
  message: { type: 'string', description: 'Short message (1-3 sentences)' },
5733
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)' },
5734
- image: { type: 'boolean', description: 'true ise son ekran görüntünü mesaja iliştirir (yoksa taze ekran yakalar) — panelde ve karşı ajanda görsel olarak açılır; grafik/ekran bulgusunu paylaşmak için kullan' },
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' },
5735
5739
  },
5736
5740
  required: ['message'],
5737
5741
  },
@@ -6547,9 +6551,17 @@ Engine.prototype._agentDmSend = async function (fromSid, args) {
6547
6551
  const to = String((args && args.to) || '').trim();
6548
6552
  const text = String((args && args.message) || '').trim().slice(0, 1200);
6549
6553
  if (!text) return { ok: false, error: 'mesaj boş' };
6550
- /* GÖRSEL DM: son ekran görüntüsü (yoksa taze yakalama) mesaja iliştirilir */
6551
- const withImage = !!(args && args.image);
6552
- const image = withImage ? await this._agentDmCaptureImage(fromSid) : '';
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
+ }
6553
6565
  const groupName = String((args && args.group) || '').replace(/\s+/g, ' ').trim().slice(0, 40);
6554
6566
  const jobs = this._bgJobs || new Map();
6555
6567
  const fromJob = jobs.get(String(fromSid));
@@ -6722,6 +6734,29 @@ Engine.prototype._agentDmSend = async function (fromSid, args) {
6722
6734
  }
6723
6735
  };
6724
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
+
6725
6760
  /* AJAN DM görseli: gönderenin oturumundaki EN SON ekran görüntüsünü (yoksa
6726
6761
  taze yakalama) data URL olarak döndürür. Çok büyük görseller DM kaydını
6727
6762
  şişirmesin diye 2.5MB üstü atlanır. */
@@ -229,7 +229,7 @@ const definitions = NAMES.map((name) => {
229
229
  },
230
230
  mt5_ea: {
231
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 computer_look screenshot\'ında GÖRÜNÜR — grafik analizini/planını panoya yazıp screenshot ile doğrula; entegrasyon bildirimleriyle birlikte kullan.',
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
233
  parameters: {
234
234
  type: 'object',
235
235
  properties: {
@@ -170,9 +170,10 @@ void ApplyLevels(const string raw)
170
170
  if(end < 0) return;
171
171
  string body = StringSubstr(raw, arr + 1, end - arr - 1);
172
172
 
173
- string names[];
174
- int n = 0;
175
- int pos = 0;
173
+ string names[];
174
+ ArrayResize(names, 64); /* FIX: dizi boyutlandirilmadan atama EA'yi dusuruyordu */
175
+ int n = 0;
176
+ int pos = 0;
176
177
  while(pos < StringLen(body))
177
178
  {
178
179
  int p = StringFind(body, "\"price\"", pos);
@@ -279,6 +280,96 @@ void ProcessCommands()
279
280
  RefreshPanel();
280
281
  result = "{\"note\":true}";
281
282
  }
283
+ else if(cmd == "shot")
284
+ {
285
+ /* ChartScreenShot -> MQL5\Files\<file> (png/gif/bmp). params:
286
+ file, width, height, timeframe (M15/H1/...), symbol (bos=aktif grafik)
287
+ Aktif grafik zaten istenen sembol+periyot ise ondan cekilir
288
+ (Beast panosu + seviye cizgileri gorunur); degilse gecici grafik acilir. */
289
+ string file = JsonGet(raw, "file");
290
+ if(StringLen(file) == 0) file = "beast_shot.png";
291
+ int w = (int)StringToInteger(JsonGet(raw, "width"));
292
+ int hh = (int)StringToInteger(JsonGet(raw, "height"));
293
+ if(w <= 0) w = 1600;
294
+ if(hh <= 0) hh = 900;
295
+
296
+ string symTxt = JsonGet(raw, "symbol");
297
+ string tfTxt = JsonGet(raw, "timeframe");
298
+
299
+ long cid = ChartID();
300
+ string symNow = ChartSymbol(cid);
301
+ ENUM_TIMEFRAMES tfNow = (ENUM_TIMEFRAMES)ChartPeriod(cid);
302
+
303
+ string symWant = symNow;
304
+ if(StringLen(symTxt) > 0 && StringCompare(symTxt, symNow) != 0)
305
+ {
306
+ if(SymbolSelect(symTxt, true)) symWant = symTxt;
307
+ }
308
+ if(SymbolSelect(symWant, true) == false)
309
+ {
310
+ ok = false;
311
+ err = "sembol bulunamadi: " + symWant;
312
+ }
313
+
314
+ ENUM_TIMEFRAMES tfWant = TfFromText(tfTxt);
315
+ ENUM_TIMEFRAMES tfUse = (tfWant == PERIOD_CURRENT) ? tfNow : tfWant;
316
+ bool tempChart = false;
317
+ bool tplApplied = false;
318
+ bool themeOk = false;
319
+ int bgNow = -1;
320
+
321
+ if(ok && (symWant != symNow || tfUse != tfNow))
322
+ {
323
+ long newId = ChartOpen(symWant, tfUse);
324
+ if(newId == 0)
325
+ {
326
+ ok = false;
327
+ err = "gecici grafik acilamadi: " + symWant;
328
+ }
329
+ else
330
+ {
331
+ cid = newId;
332
+ tempChart = true;
333
+ /* temiz gorunum: BeastFinance.tpl temasi (EA blogu cikarilmis BeastShot.tpl) */
334
+ string tpl = JsonGet(raw, "template");
335
+ if(StringLen(tpl) == 0) tpl = "BeastShot";
336
+ if(StringFind(tpl, ".") < 0) tpl = tpl + ".tpl";
337
+ bool tplOk = false;
338
+ if(StringCompare(tpl, "none.tpl") != 0)
339
+ {
340
+ tplOk = ChartApplyTemplate(cid, tpl);
341
+ ChartRedraw(cid);
342
+ Sleep(700);
343
+ }
344
+ /* sablon sembol/periyot degistirdiyse geri al */
345
+ if(StringCompare(ChartSymbol(cid), symWant) != 0 || (ENUM_TIMEFRAMES)ChartPeriod(cid) != tfUse)
346
+ ChartSetSymbolPeriod(cid, symWant, tfUse);
347
+ /* sablon bulunamasa da gorunum ayni olsun -> acik tema renklerini zorla */
348
+ themeOk = ApplyLightTheme(cid);
349
+ bgNow = (int)ChartGetInteger(cid, CHART_COLOR_BACKGROUND);
350
+ tplApplied = tplOk;
351
+ }
352
+ }
353
+
354
+ if(ok)
355
+ {
356
+ ChartRedraw(cid);
357
+ Sleep(1200); /* mumlar/olcek otursun */
358
+ ChartRedraw(cid);
359
+ Sleep(200);
360
+ if(!ChartScreenShot(cid, file, w, hh, ALIGN_RIGHT))
361
+ {
362
+ ok = false;
363
+ err = "screenshot basarisiz, hata=" + IntegerToString(GetLastError());
364
+ }
365
+ else
366
+ {
367
+ result = StringFormat("{\"file\":\"%s\",\"width\":%d,\"height\":%d,\"symbol\":\"%s\",\"period\":%d,\"temp_chart\":%s,\"template_applied\":%s,\"theme_ok\":%s,\"bg\":%d}",
368
+ JStr(file), w, hh, symWant, (int)tfUse, (tempChart ? "true" : "false"), (tplApplied ? "true" : "false"), (themeOk ? "true" : "false"), bgNow);
369
+ }
370
+ }
371
+ if(tempChart) ChartClose(cid);
372
+ }
282
373
  else
283
374
  {
284
375
  ok = false;
@@ -287,4 +378,50 @@ void ProcessCommands()
287
378
  WriteAck(id, cmd, ok, err, result);
288
379
  FileDelete(CmdFile);
289
380
  }
381
+
382
+ /* BeastFinance.tpl acik tema renklerini grafige uygular (sablon bulunamazsa da garanti) */
383
+ bool ApplyLightTheme(const long cid)
384
+ {
385
+ bool okAll = true;
386
+ okAll = ChartSetInteger(cid, CHART_COLOR_BACKGROUND, (long)16449525) && okAll;
387
+ okAll = ChartSetInteger(cid, CHART_COLOR_FOREGROUND, (long)0) && okAll;
388
+ okAll = ChartSetInteger(cid, CHART_COLOR_GRID, (long)12632256) && okAll;
389
+ okAll = ChartSetInteger(cid, CHART_COLOR_CHART_UP, (long)13434880) && okAll;
390
+ okAll = ChartSetInteger(cid, CHART_COLOR_CHART_DOWN, (long)0) && okAll;
391
+ okAll = ChartSetInteger(cid, CHART_COLOR_CHART_LINE, (long)0) && okAll;
392
+ okAll = ChartSetInteger(cid, CHART_COLOR_CANDLE_BULL, (long)13434880) && okAll;
393
+ okAll = ChartSetInteger(cid, CHART_COLOR_CANDLE_BEAR, (long)0) && okAll;
394
+ okAll = ChartSetInteger(cid, CHART_COLOR_VOLUME, (long)32768) && okAll;
395
+ okAll = ChartSetInteger(cid, CHART_COLOR_BID, (long)12632256) && okAll;
396
+ okAll = ChartSetInteger(cid, CHART_COLOR_ASK, (long)12632256) && okAll;
397
+ okAll = ChartSetInteger(cid, CHART_COLOR_LAST, (long)12632256) && okAll;
398
+ okAll = ChartSetInteger(cid, CHART_COLOR_STOP_LEVEL, (long)17919) && okAll;
399
+ okAll = ChartSetInteger(cid, CHART_MODE, CHART_CANDLES) && okAll;
400
+ okAll = ChartSetInteger(cid, CHART_SHOW_GRID, false) && okAll;
401
+ okAll = ChartSetInteger(cid, CHART_SHOW_OHLC, true) && okAll;
402
+ okAll = ChartSetInteger(cid, CHART_SHOW_BID_LINE, false) && okAll;
403
+ okAll = ChartSetInteger(cid, CHART_SHOW_ASK_LINE, false) && okAll;
404
+ okAll = ChartSetInteger(cid, CHART_SHOW_LAST_LINE, true) && okAll;
405
+ ChartRedraw(cid);
406
+ return okAll;
407
+ }
408
+
409
+ /* "M15"/"H1"/"15"/"60" gibi metni ENUM_TIMEFRAMES'e cevirir */
410
+ ENUM_TIMEFRAMES TfFromText(const string t)
411
+ {
412
+ string s = t;
413
+ StringTrimLeft(s);
414
+ StringTrimRight(s);
415
+ StringToUpper(s);
416
+ if(s == "M1" || s == "1") return PERIOD_M1;
417
+ if(s == "M5" || s == "5") return PERIOD_M5;
418
+ if(s == "M15" || s == "15") return PERIOD_M15;
419
+ if(s == "M30" || s == "30") return PERIOD_M30;
420
+ if(s == "H1" || s == "60") return PERIOD_H1;
421
+ if(s == "H4" || s == "240") return PERIOD_H4;
422
+ if(s == "D1" || s == "1440") return PERIOD_D1;
423
+ if(s == "W1") return PERIOD_W1;
424
+ if(s == "MN1") return PERIOD_MN1;
425
+ return PERIOD_CURRENT;
426
+ }
290
427
  //+------------------------------------------------------------------+
Binary file
@@ -493,6 +493,28 @@ function runSetup({ dataPath, terminalExe = '', force = false } = {}) {
493
493
  steps.push('Aktif profil grafiği bulunamadı — EA Navigator\'dan elle sürüklenebilir');
494
494
  }
495
495
 
496
+ /* 4c) BeastShot.tpl: mt5_shot geçici grafik açtığında uyguladığı temiz açık-tema
497
+ şablonu (EA bloğu yok) — kaynaktan kopyalanır; mt5_shot varsayılanı bunu arar. */
498
+ try {
499
+ const tplSrc = path.join(__dirname, 'mt5', 'BeastShot.tpl');
500
+ if (fs.existsSync(tplSrc)) {
501
+ const tplDir2 = path.join(mql5, 'Profiles', 'Templates');
502
+ const tplDst = path.join(tplDir2, 'BeastShot.tpl');
503
+ fs.mkdirSync(tplDir2, { recursive: true });
504
+ let same = false;
505
+ try { same = fs.readFileSync(tplDst).equals(fs.readFileSync(tplSrc)); } catch {}
506
+ if (!same) {
507
+ fs.copyFileSync(tplSrc, tplDst);
508
+ steps.push('Grafik şablonu yazıldı: ' + tplDst);
509
+ result.changed = true;
510
+ } else {
511
+ steps.push('Grafik şablonu güncel: ' + tplDst);
512
+ }
513
+ }
514
+ } catch (e) {
515
+ steps.push('Grafik şablonu yazılamadı: ' + String((e && e.message) || e));
516
+ }
517
+
496
518
  const running = terminalRunning();
497
519
  result.restartRequired = running && result.changed;
498
520
  if (result.restartRequired) steps.push('Değişiklikler MT5 yeniden başlatılınca etkinleşir');
@@ -62,11 +62,13 @@ Yani MT5 entegrasyonu yazarken EA'yı SIFIRDAN YAZMA, elle iliştirme varsayma
62
62
  | Dosya (MQL5\Files) | Yön | İçerik |
63
63
  |---|---|---|
64
64
  | `beast_ea.json` | EA → Beast | heartbeat: version, symbol, period, equity/balance, `terminal_trade_allowed`, `mql_trade_allowed`, positions, note |
65
- | `beast_cmd.json` | Beast → EA | komut: `{id, cmd, params}` — desteklenen: ping / chart / status / note |
65
+ | `beast_cmd.json` | Beast → EA | komut: `{id, cmd, params}` — desteklenen: ping / chart / status / note / shot |
66
66
  | `beast_cmd_ack.json` | EA → Beast | komut sonucu: `{ok, id, cmd, result|error}` |
67
67
  | `beast_note.json` | Beast → EA | grafik panosu: `{symbol?, text, levels:[{price,label}]}` — pano + yatay çizgiler; screenshot'ta görünür |
68
68
 
69
- - Finance oturumlarındaki ajan aracı: `mt5_ea` (action: status|ping|chart|note).
69
+ - GRAFİK GÖRÜNTÜSÜ: `shot` komutu `{file, width, height, symbol?, timeframe?, template?}` alır — ChartScreenShot ile GERÇEK PNG üretir. Aktif grafik istenen sembol/periyot ile aynıysa panoyu + seviye çizgilerini içerir; farklıysa EA `ChartOpen` + `BeastShot.tpl` (açık tema, EA bloğu yok) ile geçici grafik açıp kapatır. Bu komutu sarmalayan VARSAYILAN araç `tool__mt5_shot`'tır: PNG'yi çağıran ajana görsel olarak enjekte eder, `path` döndürür (send_file / agent_dm image ile paylaşılır). Seviyeleri grafikte göstermek için önce `mt5_ea note`, sonra `tool__mt5_shot` çağır — paralel ekipte teknik ajan seviyeleri çizer, görsel ajan screenshot ile teyit edip trader'a atar.
70
+
71
+ - Finance oturumlarındaki ajan aracı: `mt5_ea` (action: status|ping|chart|note); grafik PNG'si için varsayılan `tool__mt5_shot` (tüm oturumlarda açık).
70
72
  - Yeni MT5 entegrasyonu gerekiyorsa ÖNCE bu köprüyü GENİŞLET: EA'da `ProcessCommands()` içine yeni komut + Beast tarafında `mt5_ea` / python `ea_cmd`. Sıfırdan EA yazma.
71
73
  - EA kaynağı Beast uygulamasında `src/agent/mt5/BeastFinance.mq5`; güncelleme oradan yapılır, setup yeniden derler.
72
74
  - Derleme hatalarında dosya kodlaması UTF-16 (`FILE_UNICODE`) kullanılır; MQL5'te `FILE_UTF8` yoktur.
package/src/main.js CHANGED
@@ -3634,7 +3634,7 @@ app.whenReady().then(() => {
3634
3634
  reloadBackend();
3635
3635
  syncWhitelist(); // bot sistemi: whitelist.json aynası ilk açılışta garanti
3636
3636
  try { bots.ensureBotCodes(); } catch {} // her bota benzersiz 5 haneli kod garanti
3637
- try { customtools.seedIfEmpty(); } catch {} // kişisel toollar klasörü boşsa örnek tool kur
3637
+ try { customtools.seedDefaults(); customtools.seedIfEmpty(); } catch {} // varsayılan MT5 toolları + kişisel toollar klasörü boşsa örnek tool kur
3638
3638
  createSplash();
3639
3639
  createWindow();
3640
3640
  log.info('main', 'Beast Agent başlatıldı');
@@ -5253,9 +5253,17 @@ function dmMsgHtml(m) {
5253
5253
  const who = m.group
5254
5254
  ? dmName(m.fromTitle, m.from)
5255
5255
  : `${dmName(m.fromTitle, m.from)} → ${dmName(m.toTitle, m.to)}`;
5256
+ const rawImg = String(m.image || '');
5257
+ const img = /^data:image\//i.test(rawImg)
5258
+ ? `<img class="dmt-img" src="${escapeHtml(rawImg)}" alt="görsel" loading="lazy">`
5259
+ : m.imageOmitted
5260
+ ? '<div class="dmt-img-note">görsel (bütçe) kaydedilmedi</div>'
5261
+ : '';
5256
5262
  return (
5257
5263
  `<div class="dmt-msg"><span class="dm-who">${who}</span>` +
5258
- `${escapeHtml(String(m.text || ''))}<span class="dm-at">${dmTime(m.at)}</span></div>`
5264
+ `${escapeHtml(String(m.text || ''))}` +
5265
+ img +
5266
+ `<span class="dm-at">${dmTime(m.at)}</span></div>`
5259
5267
  );
5260
5268
  }
5261
5269
 
@@ -5788,6 +5796,8 @@ function onEvent(ev) {
5788
5796
  toTitle: ev.toTitle,
5789
5797
  topic: ev.topic,
5790
5798
  text: ev.text,
5799
+ ...(ev.image ? { image: ev.image } : {}),
5800
+ ...(ev.imageOmitted ? { imageOmitted: true } : {}),
5791
5801
  ...(ev.group ? { group: ev.group, groupTitle: ev.groupTitle } : {}),
5792
5802
  ...(ev.closed ? { closed: true, closedAt: ev.closedAt } : {}),
5793
5803
  };
@@ -2224,6 +2224,21 @@ body.browser-open #settingsDialog { width: min(70vw, calc(100vw - var(--bw, 480p
2224
2224
  color: var(--muted);
2225
2225
  margin-top: 2px;
2226
2226
  }
2227
+ /* ajan DM görseli (mt5_shot / ekran görüntüsü paylaşımı) */
2228
+ .dmt-msg .dmt-img {
2229
+ display: block;
2230
+ max-width: 100%;
2231
+ max-height: 260px;
2232
+ border-radius: 6px;
2233
+ margin-top: 5px;
2234
+ border: 1px solid var(--border, rgba(255,255,255,0.12));
2235
+ }
2236
+ .dmt-msg .dmt-img-note {
2237
+ font-size: 10px;
2238
+ color: var(--muted);
2239
+ font-style: italic;
2240
+ margin-top: 3px;
2241
+ }
2227
2242
  /* ajan adının yanındaki KOD rozeti (oturum id) */
2228
2243
  .dm-code {
2229
2244
  font-size: 9px; font-weight: 700; letter-spacing: 0.5px;