beast-agent 2.38.3 → 2.39.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "beast-agent",
3
3
  "productName": "Beast Agent",
4
- "version": "2.38.3",
4
+ "version": "2.39.0",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
package/src/agent/bots.js CHANGED
@@ -98,6 +98,70 @@ function adminBot() {
98
98
  };
99
99
  }
100
100
 
101
+ /* ZORUNLU VARSAYILAN BOTLAR: Beast (admin) + Trader + Coder + Tool.
102
+ Eksik olanlar OTOMATİK açılır — Tool botunun TEK görevi tool yazmaktır;
103
+ diğer ajanlar/botlar tool_request ile ona devreder (cephane mantığı). */
104
+ const DEFAULT_BOT_SEEDS = [
105
+ {
106
+ id: 'trader',
107
+ name: 'Trader',
108
+ icon: '📊',
109
+ prompt:
110
+ 'Sen BEAST Trader botsun: disiplinli bir trading analisti. Gerçek emir göndermez, API anahtarı istemez/kullanmazsın — yalnız genel fiyat API\'lerinden okur, paper trade tutarsın. Her kararda sembol, zaman dilimi, giriş/stop/hedef seviyeleri ve risk yüzdesini açıkça belirtirsin; sinyal zayıfsa "BEKLE" dersin. İzleyici (watcher) kurarak sürekli izlersin. Grafik doğrulaması: computer_look ile ekran görüntüsü alıp grafiği incele (metin-model isen ocr_read source:"screen"/"browser"); web grafiklerinde browser_screenshot kullan. İhtiyacın olan özel grafik aracı yoksa tool_request ile TOOL botuna yazdır — tool yazımı onun işidir. Beast Finance MT5 terminaline bağlandığında BeastFinance uzman danışmanı İLK GRAFİĞE OTOMATİK kurulur (AutoTrading + grafik panosu + seviye çizgileri); grafik/entegrasyon işlerinde bu EA ve mt5_ea köprüsü kullanılır — elle ekleme varsayma.',
111
+ },
112
+ {
113
+ id: 'coder',
114
+ name: 'Coder',
115
+ icon: '🤖',
116
+ prompt:
117
+ 'Sen BEAST Coder botsun: disiplinli bir yazılım mühendisi. Küçük adımlarla plan çıkarır (todo), uygular, doğrularsın. Dosya düzenler, komut çalıştırır ve kısa net açıklamalar yaparsın; gereksiz konuşmazsın. Beast Finance tarafında BeastFinance.mq5 uzman danışmanı MT5 grafiğine otomatik kurulur; MT5/entegrasyon işleri bu EA ve mt5_ea köprüsü üzerinden yürür.',
118
+ },
119
+ {
120
+ id: 'tool',
121
+ name: 'Tool',
122
+ icon: '🛠️',
123
+ prompt:
124
+ 'Sen BEAST TOOL botusun — TEK görevin TOOL YAZMAK. Başka hiçbir iş yapmazsın: analiz, genel sohbet, araştırma, kod projesi YOK; yalnız eksik/bozuk araçları yazar ve onarırsın. Her istek bir araç ihtiyacıdır. ÖNCE skill aracıyla "tool-yazma" skill\'ini oku ve sözleşmeye BİREBİR uy: araç %APPDATA%\\beast\\tools\\<slug>\\ klasöründe tool.json (tanım) + run.js (kod) olarak yaşar; slug a-z0-9_- 2-24 karakter. Sözleşme: stdin JSON argüman → stdout SADECE JSON sonuç; {ok:true,...} ya da {ok:false,error:"sebep"}; günlük/teşhis metnini console.error\'a yaz. Var olan aracı silip baştan yazma — edit_file ile GÜNCELLE. Yazdıktan sonra run_command ile TEST DÖNGÜSÜ işlet (echo \'{"mesaj":"test"}\' | node "%APPDATA%\\beast\\tools\\<slug>\\run.js"): çıktı JSON ve ok:true DEĞİLSE stderr\'i/hatayı oku → edit_file ile düzelt → YENİDEN test et; en fazla 5 deneme. Ancak test GEÇİNCE "YAYINDA" de; geçmeyen aracı yayınlama, raporda BAŞARISIZ + kök hatayı yaz. Ağ çağrılarında timeout kullan; şifre/API anahtarını ASLA loglama/yazma. İstek eksikse tek cümlelik varsayım yazıp çalışan aracı üret. Cevabın TEK paragraf: tool adı (tool__<slug>), ne yaptığı, adımlar (yazıldı → test → düzeltme → YAYINDA/BAŞARISIZ), doğrulama sonucu ve tek satır çağrı örneği. Başka ajanlara iş devretme — tool yazımı senin işin. TÜM ajanlar (Beast, finance trader/ekip, müşteri botları) tool_request ya da bot DM ile sana istek atar; her isteği araç ihtiyacı say ve sözleşmeye uygun yaz. Beast Finance ajanlarından grafik/ekran görüntüsü istekleri de gelir (ör. MT5 grafiğini PNG dosyasına kaydetme) — bunlar da araçtır, aynı sözleşmeyle yaz ve doğrula. BEASTFINANCE EA ARTIK VAR ve MT5 terminaline İLK GRAFIĞE OTOMATİK kurulur (derleme + AutoTrading dahil; kurmaya/kopyalamaya ÇALIŞMA). Beast Finance\'in ENTEGRASYON kanalı odur: heartbeat beast_ea.json, komut köprüsü beast_cmd.json/beast_cmd_ack.json, grafik panosu + seviye çizgileri beast_note.json (screenshot\'larda görünür); finance oturumlarında mt5_ea (status/ping/chart/note) aracıyla konuşulur. MT5 entegrasyonu isteği geldiğinde ÖNCE skill("mql5") oku ve köprüyü GENİŞLET (EA\'da ProcessCommands\'a yeni komut + python tarafında ea_cmd) — sıfırdan EA/script yazma.',
125
+ },
126
+ ];
127
+
128
+ /* Eksik varsayılan botları oluştur (id sabit — tool_request 'tool' id'sini arar) */
129
+ function ensureDefaultBots() {
130
+ loadRegistry();
131
+ let created = false;
132
+ for (const def of DEFAULT_BOT_SEEDS) {
133
+ if (REG.bots.some((b) => b && b.id === def.id)) continue;
134
+ if (REG.bots.length >= MAX_BOTS) break;
135
+ const bot = {
136
+ id: def.id,
137
+ name: def.name,
138
+ icon: ICONS.includes(def.icon) ? def.icon : '🤖',
139
+ admin: false,
140
+ code: '',
141
+ prompt: String(def.prompt || '').slice(0, 4000),
142
+ perm: 'all',
143
+ vis: true,
144
+ /* Tool botu araç yazıp doğrulayabilmeli: run_command/read/write açık */
145
+ skills: { ...DEFAULT_SKILLS, run_command: def.id === 'tool' },
146
+ seeBots: [],
147
+ extBrowser: false,
148
+ browserDefault: 'dahili',
149
+ extCommand: '',
150
+ plugins: [],
151
+ createdAt: nowIso(),
152
+ };
153
+ REG.bots.push(bot);
154
+ created = true;
155
+ logChange(def.id, `varsayılan bot otomatik oluşturuldu (name="${def.name}")`);
156
+ }
157
+ if (created) {
158
+ ensureBotCodes();
159
+ saveRegistry();
160
+ ensureDirs();
161
+ }
162
+ return created;
163
+ }
164
+
101
165
  /* ---------- registry ---------- */
102
166
 
103
167
  let REG = null;
@@ -114,6 +178,8 @@ function loadRegistry() {
114
178
  const beast = REG.bots.find((b) => b && b.id === 'beast');
115
179
  if (!beast) REG.bots.unshift(adminBot());
116
180
  else Object.assign(beast, { ...adminBot(), ...beast, id: 'beast', admin: true, name: beast.name || 'Beast' });
181
+ /* ZORUNLU bot seti: Beast + Trader + Coder + Tool (eksikler otomatik açılır) */
182
+ ensureDefaultBots();
117
183
  saveRegistry();
118
184
  ensureDirs();
119
185
  return REG;
@@ -149,7 +149,7 @@ function call(name, args) {
149
149
  finish({ ok: false, error: 'tool zaman aşımı (' + Math.round(TOOL_TIMEOUT_MS / 1000) + ' sn)' });
150
150
  }, TOOL_TIMEOUT_MS);
151
151
  try {
152
- child = spawn(process.execPath, [t.script], { cwd: t.path, stdio: ['pipe', 'pipe', 'pipe'] });
152
+ child = spawn(process.execPath, [t.script], { cwd: path.dirname(t.script), stdio: ['pipe', 'pipe', 'pipe'] });
153
153
  } catch (e) {
154
154
  return finish({ ok: false, error: String((e && e.message) || e) });
155
155
  }
@@ -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.
@@ -1918,6 +1921,8 @@ class Engine {
1918
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.',
1919
1922
  macro:
1920
1923
  '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
+ 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ş).',
1921
1926
  })[String((session && session.financeRole) || '')] || '';
1922
1927
  /* ROL → SKILL eşleştirmesi (ayarlar modalı): rol başına TEK ve ZORUNLU
1923
1928
  skill; ANA TRADER için ayrıca PLAYBOOK skill'i (roleSkills.trader).
@@ -1956,12 +1961,20 @@ class Engine {
1956
1961
  modeBlock2 + '\n' +
1957
1962
  (roleBlockFull ? roleBlockFull + '\n' : '') +
1958
1963
  (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' +
1964
+ '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' +
1960
1966
  'YETKİLERİN (AÇIK — çekinmeden kullan):\n' +
1961
1967
  '- 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
1968
  '- 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
1969
  '- 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' +
1970
+ '- 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' +
1976
+ '- 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' +
1965
1978
  '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
1979
  '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
1980
  (symbols ? `İZLEME LİSTESİ: ${symbols}\n` : '') +
@@ -2031,6 +2044,14 @@ class Engine {
2031
2044
  '# SKILLS\nKullanmadan önce ilgili SKILL.md dosyasını read_file ile oku.\n' + sk.join('\n')
2032
2045
  );
2033
2046
  }
2047
+ /* BOTLAR: MT5/Beast Finance entegrasyon kanalı — BeastFinance EA'sı otomatik
2048
+ kurulur; botlar grafik/entegrasyon işlerinde bunu bilerek hareket etsin */
2049
+ if (bctx) {
2050
+ parts.push(
2051
+ '# 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.'
2053
+ );
2054
+ }
2034
2055
  /* opencode instruction port (ajan modu): workspace AGENTS/CLAUDE/CONTEXT
2035
2056
  talimatları — oturum başına bir kez okunur, epoch boyunca sabit */
2036
2057
  const projChat = this._projectInstructions(session);
@@ -2072,6 +2093,7 @@ class Engine {
2072
2093
  '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
2094
  '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
2095
  '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.',
2096
+ '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
2097
  '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
2098
  '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
2099
  '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 +2775,30 @@ class Engine {
2753
2775
  const jobRec = this._bgJobs && this._bgJobs.get(String(sessionId));
2754
2776
  if (jobRec && Array.isArray(jobRec.dmInbox) && jobRec.dmInbox.length) {
2755
2777
  const inbox = jobRec.dmInbox.splice(0);
2756
- const boxText = inbox.join('\n\n').slice(0, 6000);
2778
+ const boxText = inbox
2779
+ .map((x) => (typeof x === 'string' ? x : String((x && x.text) || '')))
2780
+ .join('\n\n')
2781
+ .slice(0, 6000);
2782
+ /* görselli DM'ler: son 2 görsel bu turun mesajına vision olarak eklenir */
2783
+ const inboxImgs = inbox
2784
+ .map((x) => (x && typeof x === 'object' && /^data:image\//i.test(String(x.image || '')) ? String(x.image) : ''))
2785
+ .filter(Boolean)
2786
+ .slice(-2);
2757
2787
  const prefix =
2758
2788
  `[BEKLEYEN AJAN DM'LERİ — ${inbox.length} mesaj]\n${boxText}\n` +
2759
2789
  `(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
2790
  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 };
2791
+ msg.content = inboxImgs.length
2792
+ ? [
2793
+ { type: 'text', text: prefix + msg.content },
2794
+ ...inboxImgs.map((u) => ({ type: 'image_url', image_url: { url: u } })),
2795
+ ]
2796
+ : prefix + msg.content;
2797
+ } else if (Array.isArray(msg.content)) {
2798
+ if (msg.content[0] && typeof msg.content[0].text === 'string') {
2799
+ msg.content[0] = { ...msg.content[0], text: prefix + msg.content[0].text };
2800
+ }
2801
+ for (const u of inboxImgs) msg.content.push({ type: 'image_url', image_url: { url: u } });
2764
2802
  }
2765
2803
  }
2766
2804
  } catch {}
@@ -3604,13 +3642,21 @@ class Engine {
3604
3642
  if (this._deletedSessions && this._deletedSessions.has(sessionId)) continue;
3605
3643
  if (continuous) {
3606
3644
  job.dmInbox = job.dmInbox || [];
3607
- job.dmInbox.push(String(r.text || '').slice(0, 4000));
3645
+ job.dmInbox.push({
3646
+ text: String(r.text || '').slice(0, 4000),
3647
+ ...(r.image ? { image: String(r.image) } : {}),
3648
+ });
3608
3649
  if (job.dmInbox.length > 20) job.dmInbox.splice(0, job.dmInbox.length - 20);
3609
3650
  continue;
3610
3651
  }
3611
3652
  if (this.isBusy(sessionId)) { rest.push(r); continue; }
3612
3653
  let sent = false;
3613
- try { sent = !!this.send(sessionId, { text: r.text }); } catch {}
3654
+ try {
3655
+ const payload = { text: r.text };
3656
+ /* görselli DM: hedef ajan görüntüyü gerçekten GÖRSÜN (vision mesajı) */
3657
+ if (r.image) payload.attachments = [{ type: 'image', name: 'ekran goruntusu', dataUrl: String(r.image) }];
3658
+ sent = !!this.send(sessionId, payload);
3659
+ } catch {}
3614
3660
  if (!sent) rest.push(r);
3615
3661
  }
3616
3662
  this._pendingReports = rest;
@@ -3630,6 +3676,7 @@ class Engine {
3630
3676
  `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
3677
  (proj ? `PROJE TALİMATLARI (workspace AGENTS/CLAUDE/CONTEXT — daima uy):\n${proj}\n` : '') +
3632
3678
  `HIZ KURALLARI:\n` +
3679
+ `- 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
3680
  `- Döngülü işleri (çok URL/dosya/sayfa, tekrarlı parse-hesap) TEK python_run betiğinde topluca bitir.\n` +
3634
3681
  `- 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
3682
  `- Bağımsız araç çağrılarını aynı turda PARALEL ver.\n` +
@@ -3708,6 +3755,11 @@ class Engine {
3708
3755
  ) {
3709
3756
  toolsList = [...toolsList, AGENT_DM_DEF];
3710
3757
  }
3758
+ /* TOOL İSTEĞİ (cephane): tool botu hariç TÜM oturumlar — müşteri bot
3759
+ sohbetleri, paralel ajanlar ve finance ajanları dahil */
3760
+ if (session && !session.isBotDm && session.botId !== 'tool') {
3761
+ toolsList = [...toolsList, TOOL_REQUEST_DEF];
3762
+ }
3711
3763
  /* panel_run yalnız SANDBOX oturumlarında görünsün — diğer panellerde
3712
3764
  modelin alet çantasında olmasın (hook olmadan çalışmaz) */
3713
3765
  if (!(session && session.sbSandbox)) {
@@ -4613,33 +4665,21 @@ const skills = require('./skills');
4613
4665
  Admin bot, 5 haneli kodla başka bota özel mesaj atar; hedef botun cevabı
4614
4666
  senkron döner. İzolasyon: DM turları gizli pair oturumunda yürür, bot_dm
4615
4667
  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
4668
 
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 || '')]
4669
+ /* Deterministik pair oturumu: dm + (küçük kod + büyük kod) — aynı çift aynı oturum */
4670
+ _botPairId(senderBotId, target) {
4671
+ const sender = (typeof this.resolveBot === 'function' && senderBotId) ? this.resolveBot(senderBotId) : null;
4672
+ const codes = [String(sender ? sender.code || '' : ''), String(target && target.code || '')]
4637
4673
  .map((c) => (/^\d{5}$/.test(c) ? c : '00000'))
4638
4674
  .sort();
4639
- const pairId = 'dm' + codes[0] + codes[1];
4675
+ return 'dm' + codes[0] + codes[1];
4676
+ }
4640
4677
 
4678
+ /* Ortak bot sohbeti: hedef bota mesajı düşür, tur bitene kadar bekle, son
4679
+ asistan cevabını döndür. bot_dm (admin) + tool_request (tool botu) kullanır. */
4680
+ async _botConverse(senderBotId, target, message, signal, timeoutMs) {
4681
+ const pairId = this._botPairId(senderBotId, target);
4641
4682
  let pair = this.cache.get(pairId) || this._load(pairId);
4642
- const fresh = !pair.isBotDm && !pair.messages.length;
4643
4683
  if (!pair.isBotDm) {
4644
4684
  try {
4645
4685
  fs.appendFileSync(this._file(pairId), JSON.stringify({ t: 'botdm', a: senderBotId, b: target.id, at: nowIso() }) + '\n');
@@ -4651,24 +4691,24 @@ const skills = require('./skills');
4651
4691
  /* hedef botun kimliğiyle çalışsın; izolasyon _botSystemBlock'tan gelir */
4652
4692
  this.setSessionBot(pairId, target.id);
4653
4693
  this.setSessionPerm(pairId, target.perm || 'all');
4654
-
4694
+ const senderBot = (typeof this.resolveBot === 'function' && senderBotId) ? this.resolveBot(senderBotId) : null;
4655
4695
  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]`;
4696
+ const senderCode = senderBot && /^\d{5}$/.test(String(senderBot.code || '')) ? senderBot.code : '';
4697
+ const prefix = `[BOT DM — gönderen bot: ${senderName}${senderCode ? ' (kod ' + senderCode + ')' : ''} — cevabını kısa ve net ver, araç kullanman gerekmiyorsa kullanma]`;
4658
4698
  const before = pair.messages.length;
4659
4699
  const sent = this.send(pairId, { text: prefix + '\n' + message });
4660
4700
  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 */
4701
+ /* tur bitene kadar bekle (ctrl kaydı düşer) */
4702
+ const limit = Math.max(30000, Math.min(600000, Number(timeoutMs) || 120000));
4663
4703
  const t0 = Date.now();
4664
- while (Date.now() - t0 < 120000) {
4704
+ while (Date.now() - t0 < limit) {
4665
4705
  if (signal && signal.aborted) return { ok: false, error: 'iptal edildi' };
4666
4706
  await new Promise((r) => setTimeout(r, 400));
4667
4707
  if (!this.ctrls.has(pairId)) break;
4668
4708
  }
4669
4709
  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)' };
4710
+ try { this.interrupt(pairId, 'bot DM zaman aşımına uğradı — tur kesildi'); } catch {}
4711
+ return { ok: false, error: `hedef bot zaman aşımına uğradı (${Math.round(limit / 1000)} sn) — TOOLS panelinden sonucu takip et` };
4672
4712
  }
4673
4713
  const after = this.cache.get(pairId) || pair;
4674
4714
  const newMsgs = after.messages.slice(before);
@@ -4678,6 +4718,74 @@ const skills = require('./skills');
4678
4718
  return { ok: true, from: target.name, code: target.code, reply };
4679
4719
  }
4680
4720
 
4721
+ async _botDm(args, sessionId, signal) {
4722
+ const bots = require('./bots');
4723
+ const code = String((args && args.to) || '').replace(/\D/g, '');
4724
+ const message = String((args && args.message) || '').slice(0, 6000);
4725
+ if (!/^\d{5}$/.test(code)) return { ok: false, error: 'geçersiz kod — 5 haneli bot kodu gir' };
4726
+ if (!message.trim()) return { ok: false, error: 'mesaj boş' };
4727
+
4728
+ const session = this.cache.get(String(sessionId)) || this._load(String(sessionId));
4729
+ const senderBotId = (session && session.botId) || 'beast';
4730
+ const senderBot = (typeof this.resolveBot === 'function' && senderBotId) ? this.resolveBot(senderBotId) : null;
4731
+ const senderIsAdmin = !session.botId || !!(senderBot && senderBot.admin);
4732
+ if (!senderIsAdmin) {
4733
+ return { ok: false, error: 'yetki yok — botlar arası DM yalnız yönetici (admin) bot tarafından açılabilir' };
4734
+ }
4735
+
4736
+ const target = bots.byCode(code);
4737
+ if (!target) return { ok: false, error: `bu kotta bot yok: ${code}` };
4738
+ if (target.id === senderBotId) return { ok: false, error: 'kendine DM atılamaz' };
4739
+ return this._botConverse(senderBotId, target, message, signal, 120000);
4740
+ }
4741
+
4742
+ /* TOOL İSTEĞİ (cephane): her oturum/bot eksik aracı TOOL botuna yazdırır.
4743
+ Admin şartı YOK — amaç tüm ajanların tool üretebilmesi; tool botunun
4744
+ kendisi ve DM oturumları hariç (döngü koruması). */
4745
+ async _toolRequest(args, sessionId, signal) {
4746
+ const bots = require('./bots');
4747
+ const task = String((args && args.task) || '').slice(0, 4000);
4748
+ const context = String((args && args.context) || '').slice(0, 4000);
4749
+ if (!task.trim()) return { ok: false, error: 'task gerekli — hangi araç, ne yapacak, girdi/çıktısı ne?' };
4750
+ const session = this.cache.get(String(sessionId)) || this._load(String(sessionId));
4751
+ if (session && session.isBotDm) return { ok: false, error: 'DM oturumunda tool_request kullanılamaz' };
4752
+ const senderBotId = (session && session.botId) || 'beast';
4753
+ if (senderBotId === 'tool') return { ok: false, error: 'Tool botu kendi işini kendisi yapar — doğrudan yaz' };
4754
+ const all = bots.list() || [];
4755
+ const target = all.find((b) => b && b.id === 'tool') || all.find((b) => b && /tool/i.test(String(b.name || '')));
4756
+ if (!target) return { ok: false, error: 'Tool botu bulunamadı — botlar sayfasından oluştur' };
4757
+ const who = (() => {
4758
+ try {
4759
+ const b = typeof this.resolveBot === 'function' ? this.resolveBot(senderBotId) : null;
4760
+ return (b && b.name) || (session && session.bgTitle) || 'Beast';
4761
+ } catch { return 'Beast'; }
4762
+ })();
4763
+ const message =
4764
+ `TOOL İSTEĞİ (gönderen: ${who}):\n` + task + '\n' +
4765
+ (context ? `\nBAĞLAM (kod/yol/örnek veri/endpoint):\n` + context + '\n' : '') +
4766
+ `\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.`;
4767
+ const r = await this._botConverse(senderBotId, target, message, signal, 240000);
4768
+ /* RAPOR: tool yazım/doğrulama adımları bağlı entegrasyonlara (WA/TG/Discord) düşer */
4769
+ const notifyIntegrations = (text) => {
4770
+ try { if (typeof this.integrationNotify === 'function') this.integrationNotify(text); } catch {}
4771
+ };
4772
+ if (!r || !r.ok) {
4773
+ notifyIntegrations('🛠️ Tool botu BAŞARISIZ — isteyen: ' + who + '\nİstenen: ' + task.slice(0, 140) + '\nSebep: ' + String((r && r.error) || 'yanıt yok'));
4774
+ return r || { ok: false, error: 'Tool botu yanıt vermedi' };
4775
+ }
4776
+ notifyIntegrations(
4777
+ '🛠️ Tool botu raporu — isteyen: ' + who + '\n' +
4778
+ 'İstenen: ' + task.slice(0, 140) + '\n' +
4779
+ String(r.reply || '').replace(/\s+/g, ' ').slice(0, 500)
4780
+ );
4781
+ return {
4782
+ ok: true,
4783
+ tool_bot: r.from,
4784
+ reply: r.reply,
4785
+ note: 'Araç hazır olduğunda modele tool__<slug> olarak ANINDA açılır (doğrulama testini Tool botu yaptı; adımlar entegrasyonlara bildirildi).',
4786
+ };
4787
+ }
4788
+
4681
4789
  /* ADMIN İZLEME: tüm botlar arası DM oturumlarının listesi */
4682
4790
  listBotDmSessions() {
4683
4791
  const bots = require('./bots');
@@ -5046,7 +5154,7 @@ const skills = require('./skills');
5046
5154
  }
5047
5155
  /* AJAN DM: ajanlar arası mesaj — hedef koşan ajanın oturumuna düşer */
5048
5156
  if (name === 'agent_dm') {
5049
- return JSON.stringify(this._agentDmSend(sessionId, args));
5157
+ return JSON.stringify(await this._agentDmSend(sessionId, args));
5050
5158
  }
5051
5159
  /* KİŞİSEL TOOL: %APPDATA%\beast\tools\ — çocuk node prosesinde izole koşar */
5052
5160
  if (String(name).startsWith('tool__')) {
@@ -5236,6 +5344,11 @@ const skills = require('./skills');
5236
5344
  const r = await this._botDm(args, sessionId, signal);
5237
5345
  return JSON.stringify(r);
5238
5346
  }
5347
+ /* TOOL İSTEĞİ (cephane): eksik/bozuk aracı TOOL botuna yazdırır —
5348
+ tanım toolsList'e ekleniyordu ama dispatch'e bağlı değildi, burada bağlanır */
5349
+ if (name === 'tool_request') {
5350
+ return JSON.stringify(await this._toolRequest(args || {}, sessionId, signal));
5351
+ }
5239
5352
  if (name === 'send_file') {
5240
5353
  if (typeof this.fileSend !== 'function') {
5241
5354
  return JSON.stringify({ ok: false, error: 'dosya gönderim köprüsü yok' });
@@ -5610,7 +5723,7 @@ const AGENT_DM_DEF = {
5610
5723
  function: {
5611
5724
  name: 'agent_dm',
5612
5725
  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.',
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.',
5614
5727
  parameters: {
5615
5728
  type: 'object',
5616
5729
  properties: {
@@ -5618,12 +5731,32 @@ const AGENT_DM_DEF = {
5618
5731
  topic: { type: 'string', description: 'Short subject label for the conversation thread, e.g. "GOLD pozisyon riski"' },
5619
5732
  message: { type: 'string', description: 'Short message (1-3 sentences)' },
5620
5733
  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' },
5621
5735
  },
5622
5736
  required: ['message'],
5623
5737
  },
5624
5738
  },
5625
5739
  };
5626
5740
 
5741
+ /* TOOL İSTEĞİ (cephane kuralı): eksik/bozuk araç TOOL botuna yazdırılır.
5742
+ Tool botunun kendisi ve bot-DM oturumları hariç TÜM oturumlara açılır. */
5743
+ const TOOL_REQUEST_DEF = {
5744
+ type: 'function',
5745
+ function: {
5746
+ name: 'tool_request',
5747
+ description:
5748
+ 'İ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.',
5749
+ parameters: {
5750
+ type: 'object',
5751
+ properties: {
5752
+ task: { type: 'string', description: 'Araç ihtiyacı: ad, işlev, girdi/çıktı sözleşmesi' },
5753
+ context: { type: 'string', description: 'Örnek veri, yol, endpoint ya da hata çıktısı' },
5754
+ },
5755
+ required: ['task'],
5756
+ },
5757
+ },
5758
+ };
5759
+
5627
5760
  const TOOLS = [
5628
5761
  ...tools.definitions,
5629
5762
  {
@@ -6409,11 +6542,14 @@ Engine.prototype.clearTodos = function (sid) {
6409
6542
  /* Ajan DM gönder: hedef koşan paralel/finance ajanı (id YA DA başlık
6410
6543
  anahtar kelimesi). Teslim _pendingReports kuyruğuyla yapılır — hedef
6411
6544
  meşgulse mesaj done olunca düşer, kaybolmaz. Panel agent-dms.json'dan. */
6412
- Engine.prototype._agentDmSend = function (fromSid, args) {
6545
+ Engine.prototype._agentDmSend = async function (fromSid, args) {
6413
6546
  try {
6414
6547
  const to = String((args && args.to) || '').trim();
6415
6548
  const text = String((args && args.message) || '').trim().slice(0, 1200);
6416
6549
  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) : '';
6417
6553
  const groupName = String((args && args.group) || '').replace(/\s+/g, ' ').trim().slice(0, 40);
6418
6554
  const jobs = this._bgJobs || new Map();
6419
6555
  const fromJob = jobs.get(String(fromSid));
@@ -6538,6 +6674,7 @@ Engine.prototype._agentDmSend = function (fromSid, args) {
6538
6674
  groupTitle: g.title,
6539
6675
  topic,
6540
6676
  text,
6677
+ ...(image ? { image } : {}),
6541
6678
  };
6542
6679
  this._pushAgentDm(dm);
6543
6680
  /* canlı event TEK SEFER — üye başına emit edilirse panelde çoğalır */
@@ -6549,6 +6686,7 @@ Engine.prototype._agentDmSend = function (fromSid, args) {
6549
6686
  text:
6550
6687
  `[AJAN DM (grup: "${g.title}") — ${dm.fromTitle} · konu: "${topic}"]\n${text}\n` +
6551
6688
  `(Cevap yalnızca aksiyon/karar GEREKİYORSA ver — agent_dm group: "${g.title}", to: "${dm.fromTitle}", topic: "${topic}"; teşekkür/onay yazma.)`,
6689
+ ...(image ? { image } : {}),
6552
6690
  });
6553
6691
  this.flushPendingReports(m);
6554
6692
  }
@@ -6564,7 +6702,7 @@ Engine.prototype._agentDmSend = function (fromSid, args) {
6564
6702
 
6565
6703
  /* BİREYSEL DM — DÜZ CHAT OTURUMUNA (sahibin sohbet geçmişi) ENJEKTE
6566
6704
  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 };
6705
+ const dm = { at, from: String(fromSid), fromTitle, to: String(target), toTitle, topic, text, ...(image ? { image } : {}) };
6568
6706
  this._pushAgentDm(dm);
6569
6707
  this._persistAgentDms();
6570
6708
  emitSafe(this, target, { type: 'agent-dm', ...dm });
@@ -6574,6 +6712,7 @@ Engine.prototype._agentDmSend = function (fromSid, args) {
6574
6712
  text:
6575
6713
  `[AJAN DM — ${dm.fromTitle} · konu: "${topic}"]\n${text}\n` +
6576
6714
  `(Cevap yalnızca aksiyon/karar GEREKİYORSA ver — agent_dm to: "${dm.fromTitle}", topic: "${topic}"; teşekkür/onay yazma.)`,
6715
+ ...(image ? { image } : {}),
6577
6716
  });
6578
6717
  this.flushPendingReports(target);
6579
6718
  }
@@ -6583,6 +6722,35 @@ Engine.prototype._agentDmSend = function (fromSid, args) {
6583
6722
  }
6584
6723
  };
6585
6724
 
6725
+ /* AJAN DM görseli: gönderenin oturumundaki EN SON ekran görüntüsünü (yoksa
6726
+ taze yakalama) data URL olarak döndürür. Çok büyük görseller DM kaydını
6727
+ şişirmesin diye 2.5MB üstü atlanır. */
6728
+ Engine.prototype._agentDmCaptureImage = async function (sid) {
6729
+ let found = '';
6730
+ const sess = this.cache.get(String(sid || '')) || null;
6731
+ const msgs = (sess && Array.isArray(sess.messages) ? sess.messages : []);
6732
+ for (let i = msgs.length - 1; i >= 0 && !found; i--) {
6733
+ const c = msgs[i] && msgs[i].content;
6734
+ if (!Array.isArray(c)) continue;
6735
+ for (let j = c.length - 1; j >= 0; j--) {
6736
+ const p = c[j];
6737
+ if (p && p.type === 'image_url' && p.image_url && /^data:image\//i.test(String(p.image_url.url || ''))) {
6738
+ found = String(p.image_url.url);
6739
+ break;
6740
+ }
6741
+ }
6742
+ }
6743
+ if (!found && this.computer && typeof this.computer.look === 'function') {
6744
+ try {
6745
+ const shot = await this.computer.look();
6746
+ const url = typeof shot === 'string' ? shot : (shot && (shot.image || shot.dataUrl || shot.__injectImage)) || '';
6747
+ if (/^data:image\//i.test(String(url || ''))) found = String(url);
6748
+ } catch {}
6749
+ }
6750
+ if (!found || found.length > 2500000) return '';
6751
+ return found;
6752
+ };
6753
+
6586
6754
  /* İki ajanın ORTAK üyesi olduğu AÇIK grup var mı — 1:1 DM'leri grup içine
6587
6755
  yönlendirirken kullanılır (ekip içi trafik tek thread'de toplanır). */
6588
6756
  Engine.prototype._agentDmSharedGroup = function (a, b) {
@@ -6735,7 +6903,16 @@ Engine.prototype._persistAgentDms = function () {
6735
6903
  try {
6736
6904
  const groups = {};
6737
6905
  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));
6906
+ /* görsel bütçesi: disk şişmesin — en yeni görseller tutulur (~24MB) */
6907
+ const dms = (this._agentDms || []).slice();
6908
+ let budget = 24 * 1024 * 1024;
6909
+ for (let i = dms.length - 1; i >= 0; i--) {
6910
+ const d = dms[i];
6911
+ if (!d || typeof d.image !== 'string' || !d.image) continue;
6912
+ if (d.image.length <= budget) { budget -= d.image.length; continue; }
6913
+ dms[i] = { ...d, image: '', imageOmitted: true };
6914
+ }
6915
+ this._atomicWrite(this._agentDmsFile, JSON.stringify({ dms, groups }, null, 2));
6739
6916
  } catch {}
6740
6917
  };
6741
6918