beast-agent 2.24.2 → 2.24.5

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.24.2",
4
+ "version": "2.24.5",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
@@ -378,6 +378,8 @@ class Engine {
378
378
  } catch {
379
379
  this._agentDms = [];
380
380
  }
381
+ /* restart barışı: koşan üyesi kalmayan ekip/DM gruplarını kapat */
382
+ try { this._agentDmReconcile(); } catch {}
381
383
  /* superyorizyon döngüsü: takılan/uzun süren ajanı CEO'ya bildirir */
382
384
  this._supTimer = setInterval(() => {
383
385
  try { this._supervise(); } catch {}
@@ -1178,9 +1180,11 @@ class Engine {
1178
1180
  /* Beast Finance oturumu: kalıcı izolasyon etiketi (finoff kapatır) */
1179
1181
  session.finance = true;
1180
1182
  session.financeTrader = !!rec.trader;
1183
+ if (rec.role) session.financeRole = String(rec.role);
1181
1184
  } else if (rec.t === 'finoff') {
1182
1185
  session.finance = false;
1183
1186
  session.financeTrader = false;
1187
+ delete session.financeRole;
1184
1188
  } else if (rec.t === 'msg') {
1185
1189
  delete rec.t;
1186
1190
  session.messages.push(rec);
@@ -1331,12 +1335,17 @@ class Engine {
1331
1335
 
1332
1336
  /* Beast Finance etiketi: oturumu finance sohbeti olarak KALICI işaretler
1333
1337
  (jsonl'e 'fin' satırı düşer — restart sonrası da hatırlanır) */
1334
- markFinance(id, trader = false) {
1338
+ markFinance(id, trader = false, role = '') {
1335
1339
  const s = this._load(String(id));
1336
1340
  s.finance = true;
1337
1341
  s.financeTrader = !!trader;
1342
+ if (role) s.financeRole = String(role);
1343
+ else delete s.financeRole;
1338
1344
  try {
1339
- fs.appendFileSync(this._file(s.id), JSON.stringify({ t: 'fin', trader: !!trader, at: nowIso() }) + '\n');
1345
+ fs.appendFileSync(
1346
+ this._file(s.id),
1347
+ JSON.stringify({ t: 'fin', trader: !!trader, role: String(role || ''), at: nowIso() }) + '\n'
1348
+ );
1340
1349
  } catch {}
1341
1350
  this.cache.set(String(s.id), s);
1342
1351
  return s;
@@ -1347,6 +1356,7 @@ class Engine {
1347
1356
  const s = this._load(String(id));
1348
1357
  s.finance = false;
1349
1358
  s.financeTrader = false;
1359
+ delete s.financeRole;
1350
1360
  try {
1351
1361
  fs.appendFileSync(this._file(s.id), JSON.stringify({ t: 'finoff', at: nowIso() }) + '\n');
1352
1362
  } catch {}
@@ -1846,7 +1856,7 @@ class Engine {
1846
1856
 
1847
1857
  /* Beast Finance: MT5 trading ajanı — chat copilot'ı VEYA otonom trader.
1848
1858
  Oturum alanları (main enjekte eder): financeTrader, financeAuto,
1849
- financeSymbols, financeStrategy, financeLimits */
1859
+ financeSymbols, financeStrategy, financeLimits, financeRole */
1850
1860
  buildFinanceSystem(session) {
1851
1861
  const nowD = new Date();
1852
1862
  const localDate = nowD.toLocaleDateString('tr-TR');
@@ -1873,6 +1883,17 @@ class Engine {
1873
1883
  const modeBlock2 = isWorker
1874
1884
  ? 'ROL: FİNANS İŞÇİSİ 🔎 — TEK bir görev için piyasa verisi toplar, analiz eder, RAPOR döndürürsün. mt5_trade/mt5_pending/mt5_close KULLANMA — yalnız okuma araçları (mt5_status/account/market/positions/orders/history). İş bitince kısa final raporu yaz.'
1875
1885
  : modeBlock;
1886
+ /* ANALİZ EKİBİ rolü: trader'ın yanında koşan uzman ajanlar (risk/teknik/
1887
+ makro) — hepsi İşlem AÇMAZ, kendi uzmanlık çerçevesinde rapor yazar */
1888
+ const roleBlock =
1889
+ ({
1890
+ risk:
1891
+ '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ı).',
1892
+ technic:
1893
+ '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.',
1894
+ macro:
1895
+ '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.',
1896
+ })[String((session && session.financeRole) || '')] || '';
1876
1897
  return (
1877
1898
  (mem.soul ? mem.soul.trim() + '\n\n' : '') +
1878
1899
  'Sen BEAST FİNANS\u2019sın — bilgisayardaki MetaTrader 5 (MT5) terminaline köprüyle bağlı trading ajanı.\n' +
@@ -1880,6 +1901,7 @@ class Engine {
1880
1901
  'KÖPRÜ: MT5 terminaline Python stdio köprüsüyle bağlısın; panel 3 saniyede bir hesap/pozisyon/piyasa verisini tazeler. mt5_* araç çağrıların ve işlem hareketlerin (açılan/kapanan pozisyonlar) SAĞ paneldeki AKIŞ akışına canlı düşer — panelin orayı izlediğini bil.\n' +
1881
1902
  'GÖREV ALANI: piyasa verisi okuma (fiyat/hesap/pozisyon/geçmiş), teknik değerlendirme, pozisyon yönetimi (aç/kapat/SL/TP), risk disiplini ve kısa karar raporları. Kod yazma işi DEĞİLDİR.\n' +
1882
1903
  modeBlock2 + '\n' +
1904
+ (roleBlock ? roleBlock + '\n' : '') +
1883
1905
  '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' +
1884
1906
  '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' +
1885
1907
  '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' +
@@ -6306,25 +6328,36 @@ Engine.prototype._agentDmSend = function (fromSid, args) {
6306
6328
  const at = new Date().toISOString();
6307
6329
 
6308
6330
  /* GRUP SOHBETİ: kur/yeniden kullan, hedefi üye yap, mesaj HER ÜYEYE düşer.
6309
- Ajanlar ortak kararları burada tartışır; iş bitince sohbet kapanır. */
6310
- if (groupName) {
6311
- /* gid Türkçe-kararsız normalize: ALTIN/Altın/altın aynı grup */
6312
- const gid =
6313
- 'grp:' +
6314
- groupName
6315
- .toLowerCase()
6316
- .replace(/\u0307/g, '')
6317
- .replace(/ı/g, 'i')
6318
- .replace(/ğ/g, 'g')
6319
- .replace(/ü/g, 'u')
6320
- .replace(/ş/g, 's')
6321
- .replace(/ö/g, 'o')
6322
- .replace(/ç/g, 'c');
6323
- let g = this._agentGroups.get(gid);
6324
- if (!g) {
6325
- if (!target) return { ok: false, error: 'yeni grup için to ile en az bir ajan belirt' };
6326
- g = { id: gid, title: groupName, members: [], titles: {}, createdAt: at, closed: false };
6327
- this._agentGroups.set(gid, g);
6331
+ Ajanlar ortak kararları burada tartışır; iş bitince sohbet kapanır.
6332
+ groupName verilmemişse ama iki taraf da AYNI AÇIK grubun üyesiyse mesaj
6333
+ YİNE GRUBA düşer — üye üyeye 1:1 thread'lere ayrı ayrı dağılmaz; ekibin
6334
+ tüm iç trafiği TEK grup sohbetinde canlı toplanır. */
6335
+ const shared = groupName || !target ? null : this._agentDmSharedGroup(fromSid, target);
6336
+ let gid = '';
6337
+ if (groupName || shared) {
6338
+ let g;
6339
+ if (groupName) {
6340
+ /* gid Türkçe-kararsız normalize: ALTIN/Altın/altın aynı grup */
6341
+ gid =
6342
+ 'grp:' +
6343
+ groupName
6344
+ .toLowerCase()
6345
+ .replace(/\u0307/g, '')
6346
+ .replace(/ı/g, 'i')
6347
+ .replace(/ğ/g, 'g')
6348
+ .replace(/ü/g, 'u')
6349
+ .replace(/ş/g, 's')
6350
+ .replace(/ö/g, 'o')
6351
+ .replace(/ç/g, 'c');
6352
+ g = this._agentGroups.get(gid);
6353
+ if (!g) {
6354
+ if (!target) return { ok: false, error: 'yeni grup için to ile en az bir ajan belirt' };
6355
+ g = { id: gid, title: groupName, members: [], titles: {}, createdAt: at, closed: false };
6356
+ this._agentGroups.set(gid, g);
6357
+ }
6358
+ } else {
6359
+ g = shared;
6360
+ gid = String(g.id || '');
6328
6361
  }
6329
6362
  g.titles = g.titles || {};
6330
6363
  const addMember = (sid, title) => {
@@ -6391,6 +6424,20 @@ Engine.prototype._agentDmSend = function (fromSid, args) {
6391
6424
  }
6392
6425
  };
6393
6426
 
6427
+ /* İki ajanın ORTAK üyesi olduğu AÇIK grup var mı — 1:1 DM'leri grup içine
6428
+ yönlendirirken kullanılır (ekip içi trafik tek thread'de toplanır). */
6429
+ Engine.prototype._agentDmSharedGroup = function (a, b) {
6430
+ a = String(a || '');
6431
+ b = String(b || '');
6432
+ if (!a || !b) return null;
6433
+ for (const [, g] of this._agentGroups || new Map()) {
6434
+ if (g.closed) continue;
6435
+ const mem = g.members || [];
6436
+ if (mem.includes(a) && mem.includes(b)) return g;
6437
+ }
6438
+ return null;
6439
+ };
6440
+
6394
6441
  Engine.prototype._pushAgentDm = function (dm) {
6395
6442
  this._agentDms = this._agentDms || [];
6396
6443
  this._agentDms.push(dm);
@@ -6525,7 +6572,11 @@ Engine.prototype._agentDmClose = function (sid) {
6525
6572
  changed = true;
6526
6573
  }
6527
6574
  }
6528
- if (changed) this._persistAgentDms();
6575
+ if (changed) {
6576
+ this._persistAgentDms();
6577
+ /* panele CANLI haber: sohbet(ler) kapandı — AKTİF'ten GEÇMİŞ'e düşsün */
6578
+ try { this.emit({ type: 'agent-dm-closed', sessionId: sid }); } catch {}
6579
+ }
6529
6580
  };
6530
6581
 
6531
6582
  Engine.prototype.agentDmsList = function () {
@@ -6534,6 +6585,28 @@ Engine.prototype.agentDmsList = function () {
6534
6585
  return { dms: [...(this._agentDms || [])].slice(-600), groups };
6535
6586
  };
6536
6587
 
6588
+ /* RESTART BARİŞİ: açılışta grupları denetle — hiçbir üye ajan koşmuyorsa
6589
+ grup sohbeti KAPANIR (uygulama kapanırken yarım kalan ekip sohbetleri
6590
+ sonsuza dek "aktif" görünmesin; geçmiş panelde korunur). */
6591
+ Engine.prototype._agentDmReconcile = function () {
6592
+ const running = new Set();
6593
+ for (const [sid, j] of this._bgJobs || new Map()) {
6594
+ if (j && j.status === 'running') running.add(String(sid));
6595
+ }
6596
+ let changed = false;
6597
+ const now = new Date().toISOString();
6598
+ for (const [, g] of this._agentGroups || new Map()) {
6599
+ if (g.closed) continue;
6600
+ const anyRunning = (g.members || []).some((m) => running.has(String(m)));
6601
+ if (!anyRunning) {
6602
+ g.closed = true;
6603
+ g.closedAt = now;
6604
+ changed = true;
6605
+ }
6606
+ }
6607
+ if (changed) this._persistAgentDms();
6608
+ };
6609
+
6537
6610
  Engine.prototype.agentDmsClear = function () {
6538
6611
  this._agentDms = [];
6539
6612
  this._agentGroups = new Map();
@@ -6541,6 +6614,32 @@ Engine.prototype.agentDmsClear = function () {
6541
6614
  return { ok: true };
6542
6615
  };
6543
6616
 
6617
+ /* TEK OTURUM SİLME — renderer'daki dmThreadOf() ile BİREBİR AYNI anahtar
6618
+ hesabı: verilen key'e düşen TÜM DM'ler silinir. Grup sohbetiyse ve grup
6619
+ KAPALIYSA grup kaydı da kalkar; koşan ekip grubunun kaydı korunur ki
6620
+ süren işin zorunlu grup akışı bozulmasın. */
6621
+ Engine.prototype._agentDmKeyOf = function (dm) {
6622
+ if (dm && dm.group) return 'G|' + String(dm.group);
6623
+ const a = String((dm && dm.from) || '');
6624
+ const b = String((dm && dm.to) || '');
6625
+ const pair = a < b ? a + '|' + b : b + '|' + a;
6626
+ return pair + '|' + String((dm && dm.topic) || '(genel)').toLowerCase();
6627
+ };
6628
+
6629
+ Engine.prototype.agentDmDeleteThread = function (key) {
6630
+ key = String(key || '');
6631
+ if (!key) return { ok: false, error: 'key gerekli' };
6632
+ const before = (this._agentDms || []).length;
6633
+ this._agentDms = (this._agentDms || []).filter((dm) => this._agentDmKeyOf(dm) !== key);
6634
+ const removed = before - this._agentDms.length;
6635
+ if (key.startsWith('G|')) {
6636
+ const g = this._agentGroups && this._agentGroups.get(key.slice(2));
6637
+ if (g && g.closed) this._agentGroups.delete(g.id);
6638
+ }
6639
+ if (removed) this._persistAgentDms();
6640
+ return { ok: true, removed };
6641
+ };
6642
+
6544
6643
  module.exports = Engine;
6545
6644
  module.exports.Engine = Engine;
6546
6645
  module.exports.sanitizeTodoItems = sanitizeTodoItems;
package/src/main.js CHANGED
@@ -5890,6 +5890,7 @@ ipcMain.handle('agents:cancel', (_e, id) => {
5890
5890
  /* AJAN DM paneli: ajanlar arası mesaj arşivi */
5891
5891
  ipcMain.handle('agent-dms:list', () => (engine ? engine.agentDmsList() : []));
5892
5892
  ipcMain.handle('agent-dms:clear', () => (engine ? engine.agentDmsClear() : { ok: false }));
5893
+ ipcMain.handle('agent-dms:delete-thread', (_e, key) => (engine ? engine.agentDmDeleteThread(key) : { ok: false }));
5893
5894
 
5894
5895
  /* ---------- KİŞİSEL TOOLLAR (%APPDATA%\beast\tools\) ---------- */
5895
5896
  ipcMain.handle('tools:list', () => customtools.list());
@@ -8318,13 +8319,37 @@ function finCfg() {
8318
8319
  (eski davranış EURUSD/XAUUSD/GBPUSD/BTCUSD'yi geri geri getiriyordu) */
8319
8320
  if (!Array.isArray(f.symbols)) f.symbols = [];
8320
8321
  if (typeof f.consultChat !== 'boolean') f.consultChat = true; /* her tur öncesi chat ajanından plan */
8322
+ if (!Array.isArray(f.analysisTeam)) f.analysisTeam = []; /* trader yanında koşacak uzman roller */
8323
+ /* OTOMATİK İŞLEM hep açık — kullanıcı onay kutusu KALDIRILDI: trade ajanının
8324
+ amacı zaten işlem açmak; onay sorulmaz, limitler (max lot/pozisyon) korur */
8325
+ f.allowTrading = true;
8326
+ if (typeof f.analysisAuto !== 'boolean') f.analysisAuto = true; /* sayıya göre otomatik atama */
8327
+ if (!Number(f.analysisCount)) f.analysisCount = 2; /* varsayılan: 2 uzman ajan */
8321
8328
  if (!Number(f.intervalSec)) f.intervalSec = 120;
8322
8329
  if (!Number(f.maxLot)) f.maxLot = 0.1;
8323
8330
  if (f.maxPositions == null) f.maxPositions = 3;
8324
- if (typeof f.allowTrading !== 'boolean') f.allowTrading = false;
8325
8331
  return f;
8326
8332
  }
8327
8333
 
8334
+ /* ANALİZ EKİBİ: trader'ın yanında koşan uzman ajan rolleri — seçilen her rol
8335
+ AYRI bir sürekli finance ajanı açar. Hepsi tüm SKILL'lere + mt5 okuma
8336
+ araçlarına erişir (finance oturumu oldukları için), İŞLEM AÇMAZLAR.
8337
+ AUTO_ORDER: "sadece sayı seç" modunda gereklı ajanlar bu sırayla atanır. */
8338
+ const FIN_ROLES = [
8339
+ { id: 'risk', label: 'Risk Ajanı', desc: 'marj/kaldıraç/SL disiplini, exposure ve günlük kayıp hızı denetimi' },
8340
+ { id: 'technic', label: 'Teknik Analiz', desc: 'trend/yapı/destek-direnç/momentum okuma, AL-SAT-BEKLE önerileri' },
8341
+ { id: 'macro', label: 'Haber / Makro', desc: 'haber akışı + ekonomik takvim, DXY/emtia bağıntıları, yön eğilimi' },
8342
+ ];
8343
+ const FIN_ROLES_AUTO = ['technic', 'risk', 'macro'];
8344
+ function finRolesValid(list) {
8345
+ return (Array.isArray(list) ? list : [])
8346
+ .map((r) => String(r || '').trim())
8347
+ .filter((r) => FIN_ROLES.some((d) => d.id === r));
8348
+ }
8349
+ function finRoleDef(role) {
8350
+ return FIN_ROLES.find((d) => d.id === String(role || '')) || null;
8351
+ }
8352
+
8328
8353
  function financeLog(line) {
8329
8354
  try {
8330
8355
  fs.appendFileSync(path.join(financeDir(), 'finance.log'), `[${new Date().toISOString()}] ${line}\n`);
@@ -8451,7 +8476,8 @@ mt5bridge.on('log', (m) => {
8451
8476
  ajanlar). Döngüyü main yönetir; engine 'done'/'error'da sürekli işi
8452
8477
  KAPATMAZ (_bgFinish continuous dalı), yalnız kullanıcı iptali bitirir. */
8453
8478
 
8454
- function finAgentSession(symbols, isMain) {
8479
+ function finAgentSession(symbols, isMain, role) {
8480
+ role = String(role || '');
8455
8481
  if (isMain) {
8456
8482
  const sid = financeState.traderSid;
8457
8483
  if (sid) {
@@ -8465,7 +8491,12 @@ function finAgentSession(symbols, isMain) {
8465
8491
  const s = engine._load(engine.createSession().id);
8466
8492
  s.messages = s.messages || [];
8467
8493
  const symLabel = symbols && symbols.length ? symbols.join(', ').slice(0, 40) : 'tüm liste';
8468
- const title = isMain ? 'Beast Finance · Trader' : 'Finance · ' + symLabel;
8494
+ const roleDef = finRoleDef(role);
8495
+ const title = isMain
8496
+ ? 'Beast Finance · Trader'
8497
+ : roleDef
8498
+ ? 'Finance · ' + roleDef.label + ' · ' + symLabel
8499
+ : 'Finance · ' + symLabel;
8469
8500
  s.bgTitle = title; /* _view.isBg → sohbet geçmişinde gizli */
8470
8501
  try {
8471
8502
  fs.appendFileSync(
@@ -8474,7 +8505,7 @@ function finAgentSession(symbols, isMain) {
8474
8505
  );
8475
8506
  } catch {}
8476
8507
  engine.cache.set(s.id, s);
8477
- engine.markFinance(s.id, true); /* kalıcı finance etiketi (mt5_* araçları) */
8508
+ engine.markFinance(s.id, true, role); /* kalıcı finance etiketi + rol (mt5_* araçları) */
8478
8509
  s.workspace = financeDir(); /* ajan kendi klasöründe çalışır */
8479
8510
  if (isMain) financeState.traderSid = s.id;
8480
8511
  return s;
@@ -8487,11 +8518,14 @@ function finAgentRegisterBg(s, symbols) {
8487
8518
  if (!engine || !engine._bgJobs) return;
8488
8519
  const f = finCfg();
8489
8520
  const symLabel = symbols && symbols.length ? symbols.join(', ').slice(0, 40) : 'tüm izleme listesi';
8521
+ const roleDef = finRoleDef(s.financeRole);
8490
8522
  engine._bgJobs.set(String(s.id), {
8491
8523
  id: s.id,
8492
8524
  code: s.code,
8493
8525
  title: s.bgTitle || 'Finance Ajanı',
8494
- task: `Beast Finance ajanı (${symLabel}) — ${f.allowTrading ? 'otonom tur tur işlem yönetimi' : 'analiz modunda tur tur tarama'}`,
8526
+ task:
8527
+ `Beast Finance ${roleDef ? roleDef.label + ' (İşlem AÇMAZ — analiz ekibi)' : 'ajanı'} (${symLabel}) — ` +
8528
+ `${roleDef ? 'analiz modunda tur tur tarama' : 'otonom tur tur işlem yönetimi'}`,
8495
8529
  agent: null,
8496
8530
  parentId: '',
8497
8531
  groupId: null,
@@ -8510,29 +8544,33 @@ function finAgentRegisterBg(s, symbols) {
8510
8544
  } catch {}
8511
8545
  }
8512
8546
 
8513
- function finApplyTraderFields(s, symbolsOverride) {
8547
+ function finApplyTraderFields(s, symbolsOverride, role) {
8514
8548
  const f = finCfg();
8549
+ const r = String(role || s.financeRole || '');
8515
8550
  s.finance = true;
8516
8551
  s.financeTrader = true;
8517
- s.financeAuto = f.allowTrading === true;
8552
+ s.financeRole = r;
8553
+ /* rol ajanları ASLA işlem açmaz — trade ajanı ve işçileri DAIMA açabilir */
8554
+ s.financeAuto = !r;
8518
8555
  s.financeSymbols = Array.isArray(symbolsOverride) && symbolsOverride.length ? symbolsOverride : f.symbols;
8519
8556
  s.financeStrategy = String(f.strategy || '');
8520
8557
  s.financeLimits = { maxLot: f.maxLot, maxPositions: f.maxPositions };
8521
8558
  engine.cache.set(String(s.id), s);
8522
8559
  }
8523
8560
 
8524
- /* Yeni finance ajanı aç (ana trader YA DA sembol işçisi) */
8525
- function finAgentCreate(symbols, isMain) {
8561
+ /* Yeni finance ajanı aç (ana trader YA DA sembol işçisi YA DA analiz ekibi rolü) */
8562
+ function finAgentCreate(symbols, isMain, role) {
8526
8563
  const f = finCfg();
8527
- const s = finAgentSession(symbols, isMain);
8564
+ const s = finAgentSession(symbols, isMain, role);
8528
8565
  const record = {
8529
8566
  symbols: symbols && symbols.length ? symbols.slice(0, 10) : [...(f.symbols || [])],
8530
8567
  main: !!isMain,
8568
+ role: String(role || ''),
8531
8569
  round: 0,
8532
8570
  timer: null,
8533
8571
  };
8534
8572
  financeState.agents.set(String(s.id), record);
8535
- finApplyTraderFields(s, record.symbols);
8573
+ finApplyTraderFields(s, record.symbols, record.role);
8536
8574
  finAgentRegisterBg(s, record.symbols);
8537
8575
  return { s, agent: record };
8538
8576
  }
@@ -8566,18 +8604,22 @@ function finAgentStop(sid, reason) {
8566
8604
  function finTraderBrief(agent) {
8567
8605
  const f = finCfg();
8568
8606
  const syms = agent && agent.symbols && agent.symbols.length ? agent.symbols.join(', ') : (f.symbols || []).join(', ');
8569
- const who = agent && agent.main ? 'TRADER' : 'FINANCE AJANI';
8607
+ const roleDef = finRoleDef(agent && agent.role);
8608
+ const who = agent && agent.main ? 'TRADER' : roleDef ? roleDef.label.toUpperCase() : 'FINANCE AJANI';
8570
8609
  return [
8571
8610
  `Beast Finance ${who} başlatıldı — ilk tur: strateji çerçeveni kur ve piyasa taramasını yap.`,
8572
8611
  `Odak semboller: ${syms || '(boş — mt5_status ile terminale bak, mantıklı semboller seç)'}`,
8573
8612
  `Tur aralığı: ${f.intervalSec} sn · Max lot: ${f.maxLot} · Max eşzamanlı pozisyon: ${f.maxPositions}`,
8574
- f.allowTrading
8575
- ? 'Otomatik işlem AÇIK: mt5_trade/mt5_close/mt5_modify/mt5_pending kullanabilirsin (limitler sistemce zorlanır).'
8576
- : 'Otomatik işlem KAPALI: SADECE analiz + net işlem önerileri yaz (sembol, yön, giriş, SL, TP, sebep); işlem açma.',
8577
- f.strategy ? `Sahibinin strateji notu: ${f.strategy}` : 'Strateji notu yok: trend + destek/direnç + momentum ile temel okuma yap.',
8613
+ roleDef
8614
+ ? `UZMANLIK: ${roleDef.desc} — raporlarını bu çerçevede yaz; İŞLEM AÇMA, yalnız analiz + net öneri üret.`
8615
+ : 'Otomatik işlem AÇIK (daima): mt5_trade/mt5_close/mt5_modify/mt5_pending kullanabilirsin (limitler sistemce zorlanır).',
8616
+ roleDef
8617
+ ? 'Bulgularını agent_dm ile ANA TRADER\u2019a bildir (to: "Trader" ya da ajan başlığı anahtarı); teknik/öneri çelişkisi varsa gerekçenle yaz.'
8618
+ : f.strategy ? `Sahibinin strateji notu: ${f.strategy}` : 'Strateji notu yok: trend + destek/direnç + momentum ile temel okuma yap.',
8619
+ !roleDef && f.strategy ? `Sahibinin strateji notu: ${f.strategy}` : '',
8578
8620
  'Bu turda: mt5_status → hesap/pozisyon/fiyat verisi → değerlendirme → kararlar (veya BEKLE: sebep) → kısa rapor.',
8579
8621
  'Diğer finance/paralel ajanlarla koordinasyon için agent_dm aracı var (to: ajan başlığı anahtar kelimesi).',
8580
- ].join('\n');
8622
+ ].filter(Boolean).join('\n');
8581
8623
  }
8582
8624
 
8583
8625
  /* Her tur öncesi CHAT AJANINDAN plan iste: aynı motorun odaklı alt-ajanı
@@ -8597,7 +8639,7 @@ async function finConsultPlan(f, agent, sid) {
8597
8639
  if (ps && ps.ok) positions = (ps.data && ps.data.positions) || [];
8598
8640
  }
8599
8641
  } catch {}
8600
- const auto = f.allowTrading ? 'AÇIK (işlem açabilir)' : 'KAPALI (yalnız öneri)';
8642
+ const auto = 'AÇIK (işlem açabilir — limitler zorlanır)';
8601
8643
  const task =
8602
8644
  'Beast Finance ajanı yeni tura giriyor. SEN mt5_* araçlarını KULLANMA, işlem AÇMA — ' +
8603
8645
  'sana düşen iş: semboller için bu turun kısa işlem PLANINI üretmek ' +
@@ -8643,15 +8685,16 @@ function finAgentRound(sid) {
8643
8685
  agent.round += 1;
8644
8686
  if (agent.main) financeState.traderRounds = agent.round;
8645
8687
  const f = finCfg();
8688
+ const roleDef = finRoleDef(agent.role);
8646
8689
  try {
8647
8690
  const s = engine.cache.get(String(sid));
8648
- if (s) finApplyTraderFields(s, agent.symbols);
8691
+ if (s) finApplyTraderFields(s, agent.symbols, agent.role);
8649
8692
  } catch {}
8650
- const auto = f.allowTrading
8651
- ? 'İşlem açabilirsin — limitlere uy, SL\u2019siz pozisyon bırakma.'
8652
- : 'Otomatik işlem KAPALI — sadece analiz + öneri.';
8693
+ const auto = roleDef
8694
+ ? `UZMANLIK: ${roleDef.desc} — İŞLEM AÇMA, yalnız analiz + net öneri.`
8695
+ : 'İşlem açabilirsin — limitlere uy, SL\u2019siz pozisyon bırakma.';
8653
8696
  const focus = agent.symbols.length ? `Odak: ${agent.symbols.join(', ')}. ` : '';
8654
- const round = `FINANCE TUR #${agent.round}: ${focus}hesap + pozisyonlar + fiyatları çek; açık pozisyonları yönet (SL/TP güncelle, hedefe ulaşanı kapat); stratejine göre yeni fırsatları değerlendir. ${auto} Kısa rapor ver.`;
8697
+ const round = `FINANCE TUR #${agent.round}: ${focus}hesap + pozisyonlar + fiyatları çek; ${roleDef ? 'rolüne uygun analiz yap ve öneri ver.' : 'açık pozisyonları yönet (SL/TP güncelle, hedefe ulaşanı kapat); stratejine göre yeni fırsatları değerlendir.'} ${auto} Kısa rapor ver.`;
8655
8698
  const launch = (planBlock) => {
8656
8699
  if (!financeState.agents.has(String(sid))) return;
8657
8700
  const ok = engine.send(sid, planBlock + round, { userAction: false });
@@ -8664,8 +8707,8 @@ function finAgentRound(sid) {
8664
8707
  agent.timer = setTimeout(() => { try { finAgentRound(sid); } catch {} }, 30000);
8665
8708
  }
8666
8709
  };
8667
- if (f.consultChat === false) {
8668
- /* danışma kapalı: tur doğrudan başlar */
8710
+ if (f.consultChat === false || roleDef) {
8711
+ /* danışma kapalı ya da rol ajanı (plan trader'a yöneliktir): tur doğrudan başlar */
8669
8712
  launch('');
8670
8713
  return;
8671
8714
  }
@@ -8703,6 +8746,7 @@ ipcMain.handle('finance:state', async () => {
8703
8746
  ok: true,
8704
8747
  mode: financeState.mode,
8705
8748
  cfg: f,
8749
+ roles: FIN_ROLES,
8706
8750
  bridge: mt5bridge.status(),
8707
8751
  trader: { on: financeState.traderOn, busy, rounds: financeState.traderRounds, lastAt: financeState.lastRoundAt, sid: financeState.traderSid },
8708
8752
  models: engine ? engine.publicState().models : [],
@@ -8733,6 +8777,7 @@ ipcMain.handle('finance:snapshot', async () => {
8733
8777
  positions,
8734
8778
  symbols,
8735
8779
  cfg: f,
8780
+ roles: FIN_ROLES,
8736
8781
  trader: { on: financeState.traderOn, busy, rounds: financeState.traderRounds, lastAt: financeState.lastRoundAt },
8737
8782
  };
8738
8783
  });
@@ -8764,15 +8809,8 @@ ipcMain.handle('finance:mode', async (_e, payload) => {
8764
8809
  }
8765
8810
  } catch {}
8766
8811
  }
8767
- if (financeState.mode) {
8768
- /* OTOMATİK TRADER: finance modu açılır açılmaz trader botu devreye girer
8769
- (best-effort — model/köprü yoksa sessizce atlanır, log'a düşer) */
8770
- financeTraderStart()
8771
- .then((r) => {
8772
- if (!r || !r.ok) financeLog('[trader] otomatik başlatılamadı: ' + ((r && r.error) || 'bilinmeyen'));
8773
- })
8774
- .catch(() => {});
8775
- }
8812
+ /* OTOMATİK TRADER KAPALI: finance moduna geçmek trader'ı KENDİLİĞİNDEN
8813
+ başlatmaz — kullanıcı TRADE AJANI kartındaki ▶ ile elle başlatır */
8776
8814
  return { ok: true, mode: financeState.mode, needNew };
8777
8815
  });
8778
8816
 
@@ -8802,8 +8840,11 @@ ipcMain.handle('finance:settings', async (_e, patch) => {
8802
8840
  if (p.intervalSec !== undefined) f.intervalSec = Math.max(30, Math.min(3600, Math.round(Number(p.intervalSec) || 120)));
8803
8841
  if (p.maxLot !== undefined) f.maxLot = Math.max(0.01, Math.min(100, Number(p.maxLot) || 0.1));
8804
8842
  if (p.maxPositions !== undefined) f.maxPositions = Math.max(1, Math.min(20, Math.round(Number(p.maxPositions) || 3)));
8805
- if (p.allowTrading !== undefined) f.allowTrading = !!p.allowTrading;
8843
+ /* allowTrading artık AYARLANMAZ — daima true (finCfg zorlar) */
8806
8844
  if (p.strategy !== undefined) f.strategy = String(p.strategy || '').slice(0, 2000);
8845
+ if (p.analysisTeam !== undefined) f.analysisTeam = finRolesValid(p.analysisTeam);
8846
+ if (p.analysisAuto !== undefined) f.analysisAuto = !!p.analysisAuto;
8847
+ if (p.analysisCount !== undefined) f.analysisCount = Math.max(0, Math.min(FIN_ROLES_AUTO.length, Math.round(Number(p.analysisCount) || 0)));
8807
8848
  if (p.pythonPath !== undefined) f.pythonPath = String(p.pythonPath || '').trim();
8808
8849
  if (p.terminalPath !== undefined) f.terminalPath = String(p.terminalPath || '').trim();
8809
8850
  if (p.traderSel !== undefined) {
@@ -8860,24 +8901,58 @@ async function financeTraderStart() {
8860
8901
  financeState.lastRoundAt = Date.now();
8861
8902
  financeLog('[trader] başlatıldı (model: ' + (f.traderSel || 'genel aktif model') + ')');
8862
8903
  finPush('trader', { state: 'running', round: mainAgent.round });
8904
+ /* ANALİZ EKİBİ: seçili roller için ayrı sürekli ajanlar (koşmıyorsa) */
8905
+ finTeamStart(f);
8863
8906
  return { ok: true, sid: mainSid };
8864
8907
  }
8865
8908
 
8909
+ /* ANALİZ EKİBİ: seçili her rol için AYRI sürekli finance ajanı — hepsi
8910
+ tüm SKILL'lere + mt5 okuma araçlarına erişir, İŞLEM AÇMAZ.
8911
+ Otomatik mod: kullanıcı sadece SAYI girer → gereklı ajanlar atanır. */
8912
+ function finTeamStart(f) {
8913
+ const n = Math.max(0, Math.min(FIN_ROLES_AUTO.length, Math.round(Number(f && f.analysisCount) || 0)));
8914
+ const roles = f && f.analysisAuto
8915
+ ? FIN_ROLES_AUTO.slice(0, n)
8916
+ : finRolesValid((f && f.analysisTeam) || []);
8917
+ if (!roles.length) return;
8918
+ const running = new Set();
8919
+ for (const [, a] of financeState.agents) {
8920
+ if (a.role) running.add(a.role);
8921
+ }
8922
+ for (const roleId of roles) {
8923
+ if (running.has(roleId)) continue;
8924
+ const roleDef = finRoleDef(roleId);
8925
+ try {
8926
+ const created = finAgentCreate([], false, roleId);
8927
+ try { engine.setSessionModel(created.s.id, f.traderSel || null); } catch {}
8928
+ const ok = engine.send(created.s.id, finTraderBrief(created.agent), { userAction: true });
8929
+ if (!ok) {
8930
+ finAgentStop(String(created.s.id), 'oturum meşgul — ekip ajanı başlatılamadı');
8931
+ financeLog('[ekip] ' + roleDef.label + ' başlatılamadı: oturum meşgul');
8932
+ } else {
8933
+ financeLog('[ekip] ' + roleDef.label + ' başladı — analiz turu koşuyor (İşlem AÇMAZ)');
8934
+ }
8935
+ } catch (e) {
8936
+ financeLog('[ekip] ' + roleDef.label + ' hatası: ' + String((e && e.message) || e));
8937
+ }
8938
+ }
8939
+ }
8940
+
8866
8941
  ipcMain.handle('finance:trader:start', () => financeTraderStart());
8867
8942
 
8868
8943
  ipcMain.handle('finance:trader:stop', async () => {
8869
8944
  financeState.traderOn = false;
8870
- /* ANA trader'ı durdur (sembol işçileri rail × ile ayrı durdurulur) */
8945
+ /* ANA trader + ANALİZ EKİBİ durdurulur (sembol işçileri rail × ile ayrı durdurulur) */
8871
8946
  let mainSid = '';
8947
+ const teamSids = [];
8872
8948
  for (const [sid, a] of financeState.agents) {
8873
- if (a.main) {
8874
- mainSid = sid;
8875
- break;
8876
- }
8949
+ if (a.main) mainSid = sid;
8950
+ else if (a.role) teamSids.push(sid);
8877
8951
  }
8878
8952
  const stopped = mainSid ? finAgentStop(mainSid, 'kullanıcı Beast Finance trader ajanını durdurdu') : false;
8953
+ for (const sid of teamSids) finAgentStop(sid, 'kullanıcı Beast Finance ekip ajanını durdurdu');
8879
8954
  const interrupted = !!stopped;
8880
- financeLog('[trader] durduruldu');
8955
+ financeLog('[trader] durduruldu' + (teamSids.length ? ' (ekip: ' + teamSids.length + ' ajan)' : ''));
8881
8956
  finPush('trader', { state: 'stopped' });
8882
8957
  return { ok: true, interrupted };
8883
8958
  });
package/src/preload.js CHANGED
@@ -22,6 +22,7 @@ contextBridge.exposeInMainWorld('beast', {
22
22
  agentsCancel: (id) => ipcRenderer.invoke('agents:cancel', id),
23
23
  agentDmsList: () => ipcRenderer.invoke('agent-dms:list'),
24
24
  agentDmsClear: () => ipcRenderer.invoke('agent-dms:clear'),
25
+ agentDmDeleteThread: (key) => ipcRenderer.invoke('agent-dms:delete-thread', key),
25
26
  toolsList: () => ipcRenderer.invoke('tools:list'),
26
27
  toolSave: (t) => ipcRenderer.invoke('tools:save', t),
27
28
  toolDelete: (id) => ipcRenderer.invoke('tools:delete', id),
@@ -169,8 +169,11 @@
169
169
  <label>Max lot</label><input id="finMaxLot" type="number" min="0.01" max="100" step="0.01" />
170
170
  </div>
171
171
  <div class="fin-row fin-mini"><label>Semboller</label><button id="finSymBtn" class="fin-sym-select" title="MT5 sembol listesinden seç — tıkla">&#8230;</button></div>
172
+ <div class="fin-row fin-roles-row">
173
+ <label>Analiz ekibi</label>
174
+ <div id="finRoles" class="fin-roles" title="Trader&#39;ın yanında koşacak uzman ajanlar — tüm skill + mt5 erişimli, işlem açmazlar"></div>
175
+ </div>
172
176
  <div class="fin-row">
173
- <label class="fin-check"><input id="finAllow" type="checkbox" /> Otomatik işlem (ajan trade açabilir)</label>
174
177
  <button id="finTraderBtn" class="fin-start">&#9654;&#xFE0E; Ajanı Başlat</button>
175
178
  </div>
176
179
  <div id="finTraderStatus">Kapalı</div>
@@ -380,6 +383,7 @@
380
383
  <span class="bot-bar-btns">
381
384
  <button id="dmBackBtn" title="Oturum listesine dön" hidden>&#x25C0;&#xFE0E; Oturumlar</button>
382
385
  <button id="dmColumnBtn" title="Yan sütunda canlı akış olarak da göster">&#x25A4;&#xFE0E;</button>
386
+ <button id="dmModalDel" title="Bu oturumu sil" hidden><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M8 6V4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v2"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/></svg></button>
383
387
  <button id="dmModalClear" title="Tüm DM geçmişini sil"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M8 6V4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v2"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/></svg></button>
384
388
  <button id="dmModalClose" title="Kapat">&#x2715;&#xFE0E;</button>
385
389
  </span>
@@ -49,6 +49,7 @@ const els = {
49
49
  dmDialogList: $('#dmDialogList'),
50
50
  dmModalClose: $('#dmModalClose'),
51
51
  dmModalClear: $('#dmModalClear'),
52
+ dmModalDel: $('#dmModalDel'),
52
53
  dmColumnBtn: $('#dmColumnBtn'),
53
54
  dmBackBtn: $('#dmBackBtn'),
54
55
  watchBtn: $('#watchBtn'),
@@ -172,8 +173,8 @@ const els = {
172
173
  finInterval: $('#finInterval'),
173
174
  finMaxLot: $('#finMaxLot'),
174
175
  finSymBtn: $('#finSymBtn'),
176
+ finRoles: $('#finRoles'),
175
177
  finWatchClear: $('#finWatchClear'),
176
- finAllow: $('#finAllow'),
177
178
  finTraderBtn: $('#finTraderBtn'),
178
179
  finTraderDot: $('#finTraderDot'),
179
180
  finTraderStatus: $('#finTraderStatus'),
@@ -5035,6 +5036,7 @@ const dmState = {
5035
5036
  groups: [], // { id, title, members, titles, createdAt, closed, closedAt }
5036
5037
  modalThread: null, // modalde açık oturum key'i (null = oturum listesi)
5037
5038
  };
5039
+ let dmGroupSyncTimer = null;
5038
5040
 
5039
5041
  function dmGroupOf(id) {
5040
5042
  return (dmState.groups || []).find((g) => String(g.id) === String(id)) || null;
@@ -5058,13 +5060,24 @@ function dmThreadClosed(msgs) {
5058
5060
  return !!last.closed;
5059
5061
  }
5060
5062
 
5063
+ /* ajan adının yanına KOD (oturum id) — kimin kim olduğunu netleştirir */
5064
+ function dmCode(code) {
5065
+ const c = String(code || '').trim();
5066
+ if (!c) return '';
5067
+ return `<span class="dm-code">${escapeHtml(c.length > 14 ? c.slice(0, 14) + '…' : c)}</span>`;
5068
+ }
5069
+
5070
+ function dmName(title, code) {
5071
+ return `${escapeHtml(String(title || 'Ajan'))}${dmCode(code)}`;
5072
+ }
5073
+
5061
5074
  function dmThreadTitle(dm) {
5062
- if (dm.group) return '👥 ' + String(dm.groupTitle || 'Grup');
5063
- const a = String(dm.fromTitle || 'Ajan');
5064
- const b = String(dm.toTitle || 'Ajan');
5065
- const pair = a < b ? a + ' ↔ ' + b : b + ' ↔ ' + a;
5075
+ if (dm.group) return '👥 ' + escapeHtml(String(dm.groupTitle || 'Grup'));
5076
+ const a = dmName(dm.fromTitle, dm.from);
5077
+ const b = dmName(dm.toTitle, dm.to);
5078
+ const pair = a + ' ↔ ' + b;
5066
5079
  const topic = String(dm.topic || '(genel)');
5067
- return topic === '(genel)' ? pair : pair + ' · ' + topic;
5080
+ return topic === '(genel)' ? pair : pair + ' · ' + escapeHtml(topic);
5068
5081
  }
5069
5082
 
5070
5083
  function dmThreads() {
@@ -5087,8 +5100,8 @@ function dmTime(iso) {
5087
5100
 
5088
5101
  function dmMsgHtml(m) {
5089
5102
  const who = m.group
5090
- ? escapeHtml(String(m.fromTitle || 'Ajan'))
5091
- : `${escapeHtml(String(m.fromTitle || 'Ajan'))} → ${escapeHtml(String(m.toTitle || 'Ajan'))}`;
5103
+ ? dmName(m.fromTitle, m.from)
5104
+ : `${dmName(m.fromTitle, m.from)} → ${dmName(m.toTitle, m.to)}`;
5092
5105
  return (
5093
5106
  `<div class="dmt-msg"><span class="dm-who">${who}</span>` +
5094
5107
  `${escapeHtml(String(m.text || ''))}<span class="dm-at">${dmTime(m.at)}</span></div>`
@@ -5106,17 +5119,20 @@ function dmThreadCard(key, msgs, onClick) {
5106
5119
  if (last.group) {
5107
5120
  const g = dmGroupOf(last.group);
5108
5121
  if (g && Array.isArray(g.members) && g.members.length) {
5109
- const names = g.members.map((m) => (g.titles && g.titles[m]) || m).slice(0, 4);
5110
- membersLine = `<div class="dmt-members">👥 ${escapeHtml(names.join(', '))}</div>`;
5122
+ const names = g.members.slice(0, 4).map((m) => dmName((g.titles && g.titles[m]) || m, m));
5123
+ membersLine = `<div class="dmt-members">👥 ${names.join(', ')}</div>`;
5111
5124
  }
5112
5125
  }
5113
5126
  el.innerHTML =
5114
5127
  '<div class="dmt-head">' +
5115
- `<span class="dmt-title">${escapeHtml(dmThreadTitle(last))}</span>` +
5128
+ `<span class="dmt-title">${dmThreadTitle(last)}</span>` +
5116
5129
  `<span class="dmt-count">${closed ? 'kapandı' : msgs.length}</span>` +
5130
+ `<button class="dmt-del" title="Bu oturumu sil">&#x2715;&#xFE0E;</button>` +
5117
5131
  '</div>' +
5118
5132
  membersLine +
5119
5133
  `<div class="dmt-last">${escapeHtml(String(last.text || '').replace(/\s+/g, ' ').slice(0, 90))} · ${dmTime(last.at)}</div>`;
5134
+ const del = el.querySelector('.dmt-del');
5135
+ if (del) del.addEventListener('click', (ev) => { ev.stopPropagation(); dmDeleteThread(key); });
5120
5136
  el.addEventListener('click', onClick);
5121
5137
  return el;
5122
5138
  }
@@ -5182,6 +5198,7 @@ function renderDmModal() {
5182
5198
  list.innerHTML = '<div class="dm-empty">Henüz ajan DM\u2019i yok.<br>Ajanlar agent_dm aracıyla birbirine mesaj attıkça oturumlar birikir.</div>';
5183
5199
  if (els.dmDialogTitle) els.dmDialogTitle.textContent = 'AJAN DM — OTURUMLAR';
5184
5200
  if (els.dmBackBtn) els.dmBackBtn.hidden = true;
5201
+ if (els.dmModalDel) els.dmModalDel.hidden = true;
5185
5202
  return;
5186
5203
  }
5187
5204
  if (dmState.modalThread) {
@@ -5191,8 +5208,9 @@ function renderDmModal() {
5191
5208
  dmState.modalThread = null;
5192
5209
  return renderDmModal();
5193
5210
  }
5194
- if (els.dmDialogTitle) els.dmDialogTitle.textContent = dmThreadTitle(msgs[msgs.length - 1]);
5211
+ if (els.dmDialogTitle) els.dmDialogTitle.innerHTML = dmThreadTitle(msgs[msgs.length - 1]);
5195
5212
  if (els.dmBackBtn) els.dmBackBtn.hidden = false;
5213
+ if (els.dmModalDel) els.dmModalDel.hidden = false;
5196
5214
  list.innerHTML = '<div class="dm-thread-msgs">' + msgs.map(dmMsgHtml).join('') + '</div>';
5197
5215
  const box = list.querySelector('.dm-thread-msgs');
5198
5216
  if (box) box.scrollTop = box.scrollHeight;
@@ -5201,6 +5219,7 @@ function renderDmModal() {
5201
5219
  /* OTURUM LİSTESİ — AKTİF + GEÇMİŞ bölümleri */
5202
5220
  if (els.dmDialogTitle) els.dmDialogTitle.textContent = 'AJAN DM — AKTİF & GEÇMİŞ';
5203
5221
  if (els.dmBackBtn) els.dmBackBtn.hidden = true;
5222
+ if (els.dmModalDel) els.dmModalDel.hidden = true;
5204
5223
  list.innerHTML = '';
5205
5224
  dmAppendThreadSections(list, (key) => {
5206
5225
  dmState.modalThread = key;
@@ -5230,6 +5249,22 @@ async function dmClearAll() {
5230
5249
  toast('Ajan DM geçmişi silindi');
5231
5250
  }
5232
5251
 
5252
+ /* TEK OTURUM silme: yalnız bu sohbetin (ajan çifti + konu / grup) DM'leri gider */
5253
+ async function dmDeleteThread(key) {
5254
+ const msgs = (dmThreads().find(([k]) => k === key) || [null, []])[1];
5255
+ const last = msgs[msgs.length - 1];
5256
+ const label = last
5257
+ ? last.group
5258
+ ? String(last.groupTitle || 'Grup')
5259
+ : `${last.fromTitle || 'Ajan'} ↔ ${last.toTitle || 'Ajan'}`
5260
+ : 'bu oturum';
5261
+ if (!(await uiConfirm(`"${label}" oturumu silinsin mi?`, 'Sil'))) return;
5262
+ try { await beast.agentDmDeleteThread(key); } catch {}
5263
+ if (dmState.modalThread === key) dmState.modalThread = null;
5264
+ await refreshDmRail();
5265
+ toast('Oturum silindi');
5266
+ }
5267
+
5233
5268
  if (els.dmBtn) {
5234
5269
  els.dmBtn.addEventListener('click', () => {
5235
5270
  /* kapalıyken (dm-hidden VAR) aç: toggle 'hide' SEMANTIĞI tersine çevrilir */
@@ -5238,6 +5273,11 @@ if (els.dmBtn) {
5238
5273
  }
5239
5274
  if (els.dmModalClose) els.dmModalClose.addEventListener('click', closeDmModal);
5240
5275
  if (els.dmModalClear) els.dmModalClear.addEventListener('click', dmClearAll);
5276
+ if (els.dmModalDel) {
5277
+ els.dmModalDel.addEventListener('click', () => {
5278
+ if (dmState.modalThread) dmDeleteThread(dmState.modalThread); // açık oturumu sil
5279
+ });
5280
+ }
5241
5281
  if (els.dmBackBtn) {
5242
5282
  els.dmBackBtn.addEventListener('click', () => {
5243
5283
  dmState.modalThread = null; // oturum listesine dön
@@ -5613,10 +5653,29 @@ function onEvent(ev) {
5613
5653
  dmState.dms.push(rec);
5614
5654
  if (dmState.dms.length > 400) dmState.dms.splice(0, dmState.dms.length - 400);
5615
5655
  }
5656
+ /* GRUP mesajı canlı geldi: grup kaydını (üyeler/başlık/kapandı) sunucudan
5657
+ tazele — kart İLK mesajla birlikte tam grup görünümünde düşer, "önce
5658
+ ayrı ayrı görünür sonra gruba geçer" durumu olmaz */
5659
+ if (rec.group) {
5660
+ clearTimeout(dmGroupSyncTimer);
5661
+ dmGroupSyncTimer = setTimeout(() => {
5662
+ beast
5663
+ .agentDmsList()
5664
+ .then((res) => {
5665
+ if (res && !Array.isArray(res)) dmState.groups = res.groups || [];
5666
+ })
5667
+ .catch(() => {});
5668
+ }, 200);
5669
+ }
5616
5670
  if (els.dmOverlay && !els.dmOverlay.hidden) renderDmModal();
5617
5671
  if ($('#dmRail') && !$('#dmRail').classList.contains('dm-hidden')) renderDmRail();
5618
5672
  return;
5619
5673
  }
5674
+ /* AJAN DM sohbeti kapandı (ajan işi bitti) — AKTİF/GEÇMİŞ tazele */
5675
+ if (ev.type === 'agent-dm-closed') {
5676
+ refreshDmRail().catch(() => {});
5677
+ return;
5678
+ }
5620
5679
  /* KİŞİSEL TOOL çalışma kaydı — TOOLS konsolunun nokta göstergeleri */
5621
5680
  if (ev.type === 'tool-log') {
5622
5681
  toolUi.logRows.push({
@@ -8880,10 +8939,94 @@ function finTraderInputsSet(cfg) {
8880
8939
  if (els.finInterval && ae !== els.finInterval) els.finInterval.value = cfg.intervalSec || 120;
8881
8940
  if (els.finMaxLot && ae !== els.finMaxLot) els.finMaxLot.value = cfg.maxLot || 0.1;
8882
8941
  finSymBtnUpdate();
8883
- if (els.finAllow && ae !== els.finAllow) els.finAllow.checked = cfg.allowTrading === true;
8884
8942
  if (els.finTraderModel && ae !== els.finTraderModel && cfg.traderSel) els.finTraderModel.value = cfg.traderSel;
8885
8943
  }
8886
8944
 
8945
+ /* ANALİZ EKİBİ: trader yanında koşacak uzman ajan rolleri — İKİ MOD:
8946
+ (1) OTOMATİK: sadece SAYI gir → gereklı ajanları sistem kendisi ata;
8947
+ (2) ELLE SEÇ: rol kutucukları (Risk / Teknik / Haber-Makro) çoklu seçilir.
8948
+ Katalog sunucudan (finance snapshot/state.roles) gelir, yerel yedek var. */
8949
+ let finRolesCatalog = [
8950
+ { id: 'risk', label: 'Risk Ajanı', desc: 'marj/kaldıraç/SL disiplini, exposure ve günlük kayıp hızı denetimi' },
8951
+ { id: 'technic', label: 'Teknik Analiz', desc: 'trend/yapı/destek-direnç/momentum okuma, AL-SAT-BEKLE önerileri' },
8952
+ { id: 'macro', label: 'Haber / Makro', desc: 'haber akışı + ekonomik takvim, DXY/emtia bağıntıları, yön eğilimi' },
8953
+ ];
8954
+ let finRolesDirty = false; /* tık→kaydet arası snapshot ezmesin */
8955
+ let finRolesSyncTimer = null;
8956
+
8957
+ function finRolesSel() {
8958
+ const box = els.finRoles;
8959
+ return box ? [...box.querySelectorAll('input[data-role]:checked')].map((cb) => cb.dataset.role) : [];
8960
+ }
8961
+
8962
+ function finRenderRoles(cfg) {
8963
+ const box = els.finRoles;
8964
+ if (!box) return;
8965
+ if (finRolesDirty) return; /* bekleyen kullanıcı değişikliği korunur */
8966
+ const auto = !!(cfg && cfg.analysisAuto);
8967
+ const count = Math.max(0, Math.round(Number(cfg && cfg.analysisCount) || 0));
8968
+ const sel = cfg && Array.isArray(cfg.analysisTeam) ? cfg.analysisTeam : [];
8969
+ box.innerHTML = '';
8970
+
8971
+ /* OTOMATİK çipi + sayı girişi */
8972
+ const autoLab = document.createElement('label');
8973
+ autoLab.className = 'fin-role fin-role-auto';
8974
+ autoLab.title = 'Rol seçmeden SAYI gir — gereklı ajanları sistem otomatik atar (teknik → risk → haber sırası)';
8975
+ const autoCb = document.createElement('input');
8976
+ autoCb.type = 'checkbox';
8977
+ autoCb.checked = auto;
8978
+ autoLab.appendChild(autoCb);
8979
+ autoLab.appendChild(document.createTextNode('⚙ Otomatik'));
8980
+ autoCb.addEventListener('change', () => {
8981
+ finRolesDirty = true;
8982
+ finSaveCfg({ analysisAuto: autoCb.checked });
8983
+ toast(autoCb.checked ? 'Analiz ekibi OTOMATİK — sayı kadar gereklı ajan atanır' : 'Analiz ekibi ELLE SEÇİM moduna geçti');
8984
+ clearTimeout(finRolesSyncTimer);
8985
+ finRolesSyncTimer = setTimeout(() => { finRolesDirty = false; finSnapshot(); }, 900);
8986
+ });
8987
+ box.appendChild(autoLab);
8988
+
8989
+ const cnt = document.createElement('input');
8990
+ cnt.type = 'number';
8991
+ cnt.className = 'fin-role-count';
8992
+ cnt.min = '0';
8993
+ cnt.max = String(finRolesCatalog.length);
8994
+ cnt.step = '1';
8995
+ cnt.value = String(count || 0);
8996
+ cnt.title = 'Otomatik mod: kaç uzman ajan koşacak (0 = ekip yok)';
8997
+ cnt.disabled = !auto;
8998
+ cnt.addEventListener('change', () => {
8999
+ finRolesDirty = true;
9000
+ finSaveCfg({ analysisCount: Math.max(0, Math.round(Number(cnt.value) || 0)) });
9001
+ toast('Analiz ekibi: ' + (Math.round(Number(cnt.value) || 0) || 'kapalı') + ' uzman ajan (otomatik)');
9002
+ clearTimeout(finRolesSyncTimer);
9003
+ finRolesSyncTimer = setTimeout(() => { finRolesDirty = false; finSnapshot(); }, 900);
9004
+ });
9005
+ box.appendChild(cnt);
9006
+
9007
+ /* rol kutucukları — otomatik moddayken pasif */
9008
+ for (const r of finRolesCatalog) {
9009
+ const lab = document.createElement('label');
9010
+ lab.className = 'fin-role';
9011
+ const cb = document.createElement('input');
9012
+ cb.type = 'checkbox';
9013
+ cb.dataset.role = r.id;
9014
+ cb.checked = sel.includes(r.id);
9015
+ cb.disabled = auto;
9016
+ cb.title = r.desc + ' — tüm skill + mt5 erişimli, işlem açmaz';
9017
+ lab.appendChild(cb);
9018
+ lab.appendChild(document.createTextNode(r.label));
9019
+ cb.addEventListener('change', () => {
9020
+ finRolesDirty = true;
9021
+ finSaveCfg({ analysisTeam: finRolesSel() });
9022
+ toast(r.label + (cb.checked ? ' ekibe eklendi — sonraki başlatışta koşar' : ' ekipten çıkarıldı'));
9023
+ clearTimeout(finRolesSyncTimer);
9024
+ finRolesSyncTimer = setTimeout(() => { finRolesDirty = false; finSnapshot(); }, 900);
9025
+ });
9026
+ box.appendChild(lab);
9027
+ }
9028
+ }
9029
+
8887
9030
  function finRenderTrader(trader, cfg) {
8888
9031
  if (!els.finTraderBtn) return;
8889
9032
  const on = !!(trader && trader.on);
@@ -8900,10 +9043,14 @@ function finRenderTrader(trader, cfg) {
8900
9043
  const d = new Date(trader.lastAt);
8901
9044
  t = ' · son tur ' + String(d.getHours()).padStart(2, '0') + ':' + String(d.getMinutes()).padStart(2, '0');
8902
9045
  }
8903
- const mode = cfg && cfg.allowTrading ? 'OTOMATİK' : 'analiz modu';
9046
+ /* ekip bilgisi: otomatik modda sayı, elle seçimde seçili rol sayısı */
9047
+ const team = cfg && cfg.analysisAuto
9048
+ ? Math.round(Number(cfg.analysisCount) || 0)
9049
+ : (cfg && Array.isArray(cfg.analysisTeam) ? cfg.analysisTeam.length : 0);
9050
+ const teamTxt = team ? ' · ekip ' + team + ' ajan' + (cfg && cfg.analysisAuto ? ' (oto)' : '') : '';
8904
9051
  els.finTraderStatus.textContent = on
8905
- ? (trader.busy ? 'Tur çalışıyor…' : 'Beklemede — sıradaki tur ' + (cfg && cfg.intervalSec ? cfg.intervalSec + ' sn' : '')) + ' · tur #' + (trader.rounds || 0) + ' · ' + mode + t
8906
- : 'Kapalı · ' + mode;
9052
+ ? (trader.busy ? 'Tur çalışıyor…' : 'Beklemede — sıradaki tur ' + (cfg && cfg.intervalSec ? cfg.intervalSec + ' sn' : '')) + ' · tur #' + (trader.rounds || 0) + ' · OTONOM' + teamTxt + t
9053
+ : 'Kapalı · OTONOM' + teamTxt;
8907
9054
  }
8908
9055
  }
8909
9056
 
@@ -8941,7 +9088,9 @@ async function finSnapshot() {
8941
9088
  finRenderAccount(r.account);
8942
9089
  finRenderPositions(r.positions || []);
8943
9090
  finRenderSymbols(r.symbols || []);
9091
+ if (Array.isArray(r.roles) && r.roles.length) finRolesCatalog = r.roles;
8944
9092
  finTraderInputsSet(r.cfg);
9093
+ finRenderRoles(r.cfg);
8945
9094
  finRenderTrader(r.trader, r.cfg);
8946
9095
  }
8947
9096
 
@@ -9037,22 +9186,7 @@ function finSaveCfg(patch) {
9037
9186
  if (els.finInterval) els.finInterval.addEventListener('change', () => finSaveCfg({ intervalSec: Number(els.finInterval.value) || 120 }));
9038
9187
  if (els.finMaxLot) els.finMaxLot.addEventListener('change', () => finSaveCfg({ maxLot: Number(els.finMaxLot.value) || 0.1 }));
9039
9188
  if (els.finSymBtn) els.finSymBtn.addEventListener('click', finSymPickerOpen);
9040
- if (els.finAllow) {
9041
- els.finAllow.addEventListener('change', async () => {
9042
- if (els.finAllow.checked) {
9043
- const ok = await uiConfirm(
9044
- 'OTOMATİK İŞLEM açılıyor: trade ajanı GERÇEK hesapta emir açabilir/kapatabilir (max lot + pozisyon limitleriyle). Onaylıyor musun?',
9045
- 'Aç',
9046
- 'Vazgeç'
9047
- );
9048
- if (!ok) {
9049
- els.finAllow.checked = false;
9050
- return;
9051
- }
9052
- }
9053
- finSaveCfg({ allowTrading: els.finAllow.checked });
9054
- });
9055
- }
9189
+ /* OTOMATİK İŞLEM onay kutusu KALDIRILDI — trade ajanı daima işlem açabilir */
9056
9190
  if (els.finTraderModel) {
9057
9191
  els.finTraderModel.addEventListener('change', () => {
9058
9192
  finSaveCfg({ traderSel: els.finTraderModel.value });
@@ -9066,7 +9200,14 @@ if (els.finTraderBtn) {
9066
9200
  } else {
9067
9201
  const r = await beast.financeTraderStart().catch((e) => ({ ok: false, error: String((e && e.message) || e) }));
9068
9202
  if (r && !r.ok) toast(r.error || 'Trader başlatılamadı');
9069
- else if (r && r.ok) toast('Trade ajanı başladı — turlar panelde izlenir');
9203
+ else if (r && r.ok) {
9204
+ const cfg = (await beast.financeState().catch(() => null));
9205
+ const c = (cfg && cfg.cfg) || {};
9206
+ const team = c.analysisAuto
9207
+ ? Math.round(Number(c.analysisCount) || 0)
9208
+ : finRolesSel().length;
9209
+ toast(team ? 'Trade ajanı + analiz ekibi (' + team + ' uzman) başladı — turlar panelde izlenir' : 'Trade ajanı başladı — turlar panelde izlenir');
9210
+ }
9070
9211
  }
9071
9212
  finSnapshot();
9072
9213
  });
@@ -2156,6 +2156,15 @@ body.browser-open #settingsDialog { width: min(70vw, calc(100vw - var(--bw, 480p
2156
2156
  background: var(--accent-dim); color: var(--accent);
2157
2157
  border-radius: 8px; padding: 1px 7px; font-weight: 700;
2158
2158
  }
2159
+ /* oturum kartındaki TEK SİL butonu */
2160
+ .dmt-del {
2161
+ flex: none; border: none; background: transparent;
2162
+ color: var(--muted); font-size: 11px; line-height: 1;
2163
+ padding: 2px 4px; border-radius: 5px; cursor: pointer;
2164
+ opacity: 0; transition: opacity 0.12s ease;
2165
+ }
2166
+ .dmt:hover .dmt-del { opacity: 0.75; }
2167
+ .dmt-del:hover { opacity: 1; color: #e5484d; background: var(--panel2); }
2159
2168
  .dmt-last {
2160
2169
  font-size: 11px; margin-top: 4px;
2161
2170
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
@@ -2202,6 +2211,16 @@ body.browser-open #settingsDialog { width: min(70vw, calc(100vw - var(--bw, 480p
2202
2211
  color: var(--muted);
2203
2212
  margin-top: 2px;
2204
2213
  }
2214
+ /* ajan adının yanındaki KOD rozeti (oturum id) */
2215
+ .dm-code {
2216
+ font-size: 9px; font-weight: 700; letter-spacing: 0.5px;
2217
+ color: var(--accent);
2218
+ background: var(--accent-dim);
2219
+ border-radius: 6px; padding: 0 4px; margin-left: 4px;
2220
+ vertical-align: 1px;
2221
+ }
2222
+ .dmt-title .dm-code { margin-left: 3px; }
2223
+ .dmt-members .dm-code { margin-left: 2px; }
2205
2224
 
2206
2225
  /* ---------- AJAN DM modalı (✉) ---------- */
2207
2226
  #dmOverlay {
@@ -4845,19 +4864,60 @@ body.finance-mode #composerWrap { margin-right: var(--finW, 400px); }
4845
4864
  text-overflow: ellipsis;
4846
4865
  }
4847
4866
  .fin-check input { flex: none; width: auto; }
4867
+ /* ANALİZ EKİBİ: rol seçim kutuları (Risk / Teknik / Makro) */
4868
+ .fin-roles-row label { flex: none; font-size: 10px; color: var(--muted); min-width: 44px; }
4869
+ .fin-roles {
4870
+ flex: 1; min-width: 0;
4871
+ display: flex; flex-wrap: wrap; gap: 4px;
4872
+ }
4873
+ .fin-role {
4874
+ flex: none;
4875
+ display: inline-flex; align-items: center; gap: 4px;
4876
+ font-size: 10.5px; color: var(--text);
4877
+ background: var(--panel2);
4878
+ border: 1px solid var(--border);
4879
+ border-radius: 8px;
4880
+ padding: 2px 8px 2px 5px;
4881
+ cursor: pointer;
4882
+ user-select: none;
4883
+ }
4884
+ .fin-role:hover { border-color: var(--accent); }
4885
+ .fin-role:has(input:checked) { border-color: var(--accent); color: var(--accent); background: var(--accent-dim); }
4886
+ .fin-role input { flex: none; width: auto; margin: 0; }
4887
+ .fin-role input:disabled { opacity: 0.45; cursor: default; }
4888
+ .fin-role-auto { font-weight: 700; }
4889
+ /* Otomatik moddaki SAYI girişi */
4890
+ .fin-role-count {
4891
+ flex: none; width: 44px;
4892
+ background: var(--panel2); color: var(--text);
4893
+ border: 1px solid var(--border); border-radius: 8px;
4894
+ font-size: 11px; font-weight: 700; text-align: center;
4895
+ padding: 2px 3px;
4896
+ }
4897
+ .fin-role-count:focus { border-color: var(--accent); outline: none; }
4898
+ .fin-role-count:disabled { opacity: 0.45; }
4848
4899
  .fin-start {
4849
4900
  flex: none;
4850
4901
  background: var(--accent);
4851
- border: none;
4852
- color: #fff;
4902
+ border: 1px solid var(--accent);
4903
+ /* dark modda accent beyazımsı olur — sabit beyaz yazı butonda kaybolurdu;
4904
+ --accent-contrast iki temada da doğru kontrastı verir (siyah↔beyaz) */
4905
+ color: var(--accent-contrast);
4853
4906
  font-size: 11px;
4854
- font-weight: 700;
4855
- padding: 6px 12px;
4907
+ font-weight: 800;
4908
+ letter-spacing: 0.2px;
4909
+ padding: 6px 14px;
4856
4910
  border-radius: 7px;
4857
4911
  cursor: pointer;
4912
+ transition: filter 0.12s ease, box-shadow 0.12s ease, transform 0.06s ease;
4913
+ }
4914
+ .fin-start:hover { filter: brightness(1.08); box-shadow: 0 1px 6px rgba(0, 0, 0, 0.22); }
4915
+ .fin-start:active { transform: translateY(1px); filter: brightness(0.96); }
4916
+ .fin-start.stop {
4917
+ background: #ef4444;
4918
+ border-color: #ef4444;
4919
+ color: #fff; /* kırmızı zeminde beyaz her iki temada da okunur */
4858
4920
  }
4859
- .fin-start:hover { filter: brightness(1.1); }
4860
- .fin-start.stop { background: #ef4444; }
4861
4921
 
4862
4922
  #finTraderStatus {
4863
4923
  margin-top: 7px;