beast-agent 2.24.3 → 2.30.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.24.3",
4
+ "version": "2.30.0",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
@@ -1180,9 +1180,11 @@ class Engine {
1180
1180
  /* Beast Finance oturumu: kalıcı izolasyon etiketi (finoff kapatır) */
1181
1181
  session.finance = true;
1182
1182
  session.financeTrader = !!rec.trader;
1183
+ if (rec.role) session.financeRole = String(rec.role);
1183
1184
  } else if (rec.t === 'finoff') {
1184
1185
  session.finance = false;
1185
1186
  session.financeTrader = false;
1187
+ delete session.financeRole;
1186
1188
  } else if (rec.t === 'msg') {
1187
1189
  delete rec.t;
1188
1190
  session.messages.push(rec);
@@ -1333,12 +1335,17 @@ class Engine {
1333
1335
 
1334
1336
  /* Beast Finance etiketi: oturumu finance sohbeti olarak KALICI işaretler
1335
1337
  (jsonl'e 'fin' satırı düşer — restart sonrası da hatırlanır) */
1336
- markFinance(id, trader = false) {
1338
+ markFinance(id, trader = false, role = '') {
1337
1339
  const s = this._load(String(id));
1338
1340
  s.finance = true;
1339
1341
  s.financeTrader = !!trader;
1342
+ if (role) s.financeRole = String(role);
1343
+ else delete s.financeRole;
1340
1344
  try {
1341
- 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
+ );
1342
1349
  } catch {}
1343
1350
  this.cache.set(String(s.id), s);
1344
1351
  return s;
@@ -1349,6 +1356,7 @@ class Engine {
1349
1356
  const s = this._load(String(id));
1350
1357
  s.finance = false;
1351
1358
  s.financeTrader = false;
1359
+ delete s.financeRole;
1352
1360
  try {
1353
1361
  fs.appendFileSync(this._file(s.id), JSON.stringify({ t: 'finoff', at: nowIso() }) + '\n');
1354
1362
  } catch {}
@@ -1848,7 +1856,7 @@ class Engine {
1848
1856
 
1849
1857
  /* Beast Finance: MT5 trading ajanı — chat copilot'ı VEYA otonom trader.
1850
1858
  Oturum alanları (main enjekte eder): financeTrader, financeAuto,
1851
- financeSymbols, financeStrategy, financeLimits */
1859
+ financeSymbols, financeStrategy, financeLimits, financeRole */
1852
1860
  buildFinanceSystem(session) {
1853
1861
  const nowD = new Date();
1854
1862
  const localDate = nowD.toLocaleDateString('tr-TR');
@@ -1875,6 +1883,21 @@ class Engine {
1875
1883
  const modeBlock2 = isWorker
1876
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.'
1877
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) || '')] || '';
1897
+ /* FİNANS EKİBİ: tüm finance ajanları tek DM grubundadır — sohbet grup
1898
+ thread'inde toplanır (ayrı ayrı 1:1 thread'lere dağılmaz) */
1899
+ const finJob = this._bgJobs && this._bgJobs.get(String(session && session.id));
1900
+ const teamLine = finJob && finJob.teamGid ? this._agentTeamPromptLine(finJob.teamGid) : '';
1878
1901
  return (
1879
1902
  (mem.soul ? mem.soul.trim() + '\n\n' : '') +
1880
1903
  'Sen BEAST FİNANS\u2019sın — bilgisayardaki MetaTrader 5 (MT5) terminaline köprüyle bağlı trading ajanı.\n' +
@@ -1882,6 +1905,8 @@ class Engine {
1882
1905
  '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' +
1883
1906
  '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' +
1884
1907
  modeBlock2 + '\n' +
1908
+ (roleBlock ? roleBlock + '\n' : '') +
1909
+ (teamLine || '') +
1885
1910
  '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' +
1886
1911
  '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' +
1887
1912
  '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' +
@@ -6308,25 +6333,36 @@ Engine.prototype._agentDmSend = function (fromSid, args) {
6308
6333
  const at = new Date().toISOString();
6309
6334
 
6310
6335
  /* GRUP SOHBETİ: kur/yeniden kullan, hedefi üye yap, mesaj HER ÜYEYE düşer.
6311
- Ajanlar ortak kararları burada tartışır; iş bitince sohbet kapanır. */
6312
- if (groupName) {
6313
- /* gid Türkçe-kararsız normalize: ALTIN/Altın/altın aynı grup */
6314
- const gid =
6315
- 'grp:' +
6316
- groupName
6317
- .toLowerCase()
6318
- .replace(/\u0307/g, '')
6319
- .replace(/ı/g, 'i')
6320
- .replace(/ğ/g, 'g')
6321
- .replace(/ü/g, 'u')
6322
- .replace(/ş/g, 's')
6323
- .replace(/ö/g, 'o')
6324
- .replace(/ç/g, 'c');
6325
- let g = this._agentGroups.get(gid);
6326
- if (!g) {
6327
- if (!target) return { ok: false, error: 'yeni grup için to ile en az bir ajan belirt' };
6328
- g = { id: gid, title: groupName, members: [], titles: {}, createdAt: at, closed: false };
6329
- this._agentGroups.set(gid, g);
6336
+ Ajanlar ortak kararları burada tartışır; iş bitince sohbet kapanır.
6337
+ groupName verilmemişse ama iki taraf da AYNI AÇIK grubun üyesiyse mesaj
6338
+ YİNE GRUBA düşer — üye üyeye 1:1 thread'lere ayrı ayrı dağılmaz; ekibin
6339
+ tüm iç trafiği TEK grup sohbetinde canlı toplanır. */
6340
+ const shared = groupName || !target ? null : this._agentDmSharedGroup(fromSid, target);
6341
+ let gid = '';
6342
+ if (groupName || shared) {
6343
+ let g;
6344
+ if (groupName) {
6345
+ /* gid Türkçe-kararsız normalize: ALTIN/Altın/altın aynı grup */
6346
+ gid =
6347
+ 'grp:' +
6348
+ groupName
6349
+ .toLowerCase()
6350
+ .replace(/\u0307/g, '')
6351
+ .replace(/ı/g, 'i')
6352
+ .replace(/ğ/g, 'g')
6353
+ .replace(/ü/g, 'u')
6354
+ .replace(/ş/g, 's')
6355
+ .replace(/ö/g, 'o')
6356
+ .replace(/ç/g, 'c');
6357
+ g = this._agentGroups.get(gid);
6358
+ if (!g) {
6359
+ if (!target) return { ok: false, error: 'yeni grup için to ile en az bir ajan belirt' };
6360
+ g = { id: gid, title: groupName, members: [], titles: {}, createdAt: at, closed: false };
6361
+ this._agentGroups.set(gid, g);
6362
+ }
6363
+ } else {
6364
+ g = shared;
6365
+ gid = String(g.id || '');
6330
6366
  }
6331
6367
  g.titles = g.titles || {};
6332
6368
  const addMember = (sid, title) => {
@@ -6393,6 +6429,20 @@ Engine.prototype._agentDmSend = function (fromSid, args) {
6393
6429
  }
6394
6430
  };
6395
6431
 
6432
+ /* İki ajanın ORTAK üyesi olduğu AÇIK grup var mı — 1:1 DM'leri grup içine
6433
+ yönlendirirken kullanılır (ekip içi trafik tek thread'de toplanır). */
6434
+ Engine.prototype._agentDmSharedGroup = function (a, b) {
6435
+ a = String(a || '');
6436
+ b = String(b || '');
6437
+ if (!a || !b) return null;
6438
+ for (const [, g] of this._agentGroups || new Map()) {
6439
+ if (g.closed) continue;
6440
+ const mem = g.members || [];
6441
+ if (mem.includes(a) && mem.includes(b)) return g;
6442
+ }
6443
+ return null;
6444
+ };
6445
+
6396
6446
  Engine.prototype._pushAgentDm = function (dm) {
6397
6447
  this._agentDms = this._agentDms || [];
6398
6448
  this._agentDms.push(dm);
@@ -6403,18 +6453,30 @@ Engine.prototype._pushAgentDm = function (dm) {
6403
6453
  ortak DM grubuna girer — tartışma ZORUNLU (sistem promptuyla). ---------- */
6404
6454
 
6405
6455
  /* İş başlarken çağrılır: aynı parent / bg-gruptan başka ajan varsa (varsa
6406
- önceden kurulmuştur) hepsini TEK ekip grubuna katar + katılım postu düşer. */
6456
+ önceden kurulmuştur) hepsini TEK ekip grubuna katar + katılım postu düşer.
6457
+ dmGroupId verilirse (finans ekibi gibi hazır grup) parent şartı aranmaz —
6458
+ aynı dmGroupId'li TÜM ajanlar tek gruba toplanır. */
6407
6459
  Engine.prototype._agentTeamJoin = function (job) {
6408
6460
  try {
6409
6461
  if (!job || !job.id) return;
6410
6462
  const parent = String(job.parentId || '');
6411
- if (!parent) return;
6412
- const gid = 'team:' + (job.groupId || parent);
6463
+ const explicit = String(job.dmGroupId || '');
6464
+ if (!parent && !explicit) return;
6465
+ const gid = explicit || 'team:' + (job.groupId || parent);
6413
6466
  const base =
6414
- String(job.task || job.title || 'ortak görev').replace(/\s+/g, ' ').trim().slice(0, 28) || 'ortak görev';
6467
+ String(job.dmGroupTitle || job.task || job.title || 'ortak görev').replace(/\s+/g, ' ').trim().slice(0, 40) ||
6468
+ 'ortak görev';
6415
6469
  let g = this._agentGroups.get(gid);
6416
6470
  if (!g) {
6417
- g = { id: gid, title: base + ' EKİP', members: [], titles: {}, createdAt: nowIso(), closed: false, team: true };
6471
+ g = {
6472
+ id: gid,
6473
+ title: job.dmGroupTitle ? base : base + ' EKİP',
6474
+ members: [],
6475
+ titles: {},
6476
+ createdAt: nowIso(),
6477
+ closed: false,
6478
+ team: true,
6479
+ };
6418
6480
  this._agentGroups.set(gid, g);
6419
6481
  }
6420
6482
  g.closed = false;
@@ -6425,9 +6487,13 @@ Engine.prototype._agentTeamJoin = function (job) {
6425
6487
  if (!g.members.includes(id)) g.members.push(id);
6426
6488
  if (title) g.titles[id] = String(title);
6427
6489
  };
6428
- /* aynı ortak görevdeki DİĞER ajanlar (aborted olanlar hariç) */
6490
+ /* aynı ortak görevdeki / aynı hazır gruptaki DİĞER ajanlar (aborted hariç) */
6429
6491
  const mates = [...this._bgJobs.values()].filter(
6430
- (j) => j && String(j.id) !== String(job.id) && String(j.parentId) === parent && j.status !== 'aborted'
6492
+ (j) =>
6493
+ j &&
6494
+ String(j.id) !== String(job.id) &&
6495
+ j.status !== 'aborted' &&
6496
+ (explicit ? String(j.dmGroupId || '') === explicit : String(j.parentId) === parent)
6431
6497
  );
6432
6498
  add(job.id, job.title);
6433
6499
  for (const m of mates) add(m.id, m.title);
@@ -6569,6 +6635,32 @@ Engine.prototype.agentDmsClear = function () {
6569
6635
  return { ok: true };
6570
6636
  };
6571
6637
 
6638
+ /* TEK OTURUM SİLME — renderer'daki dmThreadOf() ile BİREBİR AYNI anahtar
6639
+ hesabı: verilen key'e düşen TÜM DM'ler silinir. Grup sohbetiyse ve grup
6640
+ KAPALIYSA grup kaydı da kalkar; koşan ekip grubunun kaydı korunur ki
6641
+ süren işin zorunlu grup akışı bozulmasın. */
6642
+ Engine.prototype._agentDmKeyOf = function (dm) {
6643
+ if (dm && dm.group) return 'G|' + String(dm.group);
6644
+ const a = String((dm && dm.from) || '');
6645
+ const b = String((dm && dm.to) || '');
6646
+ const pair = a < b ? a + '|' + b : b + '|' + a;
6647
+ return pair + '|' + String((dm && dm.topic) || '(genel)').toLowerCase();
6648
+ };
6649
+
6650
+ Engine.prototype.agentDmDeleteThread = function (key) {
6651
+ key = String(key || '');
6652
+ if (!key) return { ok: false, error: 'key gerekli' };
6653
+ const before = (this._agentDms || []).length;
6654
+ this._agentDms = (this._agentDms || []).filter((dm) => this._agentDmKeyOf(dm) !== key);
6655
+ const removed = before - this._agentDms.length;
6656
+ if (key.startsWith('G|')) {
6657
+ const g = this._agentGroups && this._agentGroups.get(key.slice(2));
6658
+ if (g && g.closed) this._agentGroups.delete(g.id);
6659
+ }
6660
+ if (removed) this._persistAgentDms();
6661
+ return { ok: true, removed };
6662
+ };
6663
+
6572
6664
  module.exports = Engine;
6573
6665
  module.exports.Engine = Engine;
6574
6666
  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,14 +8518,21 @@ 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';
8490
- engine._bgJobs.set(String(s.id), {
8521
+ const roleDef = finRoleDef(s.financeRole);
8522
+ const job = {
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,
8532
+ /* FİNANS EKİBİ DM GRUBU: trader + analiz ekibi + sembol işçileri TEK
8533
+ gruba girer — mesajlar ayrı ayrı 1:1 thread'lere dağılmaz */
8534
+ dmGroupId: 'team:finance',
8535
+ dmGroupTitle: 'Beast Finance EKİP',
8498
8536
  status: 'running',
8499
8537
  slot: false, /* eşzamanlılık slotu TÜKETMEZ — ana işleri bloklamaz */
8500
8538
  continuous: true, /* tur sonları işi KAPATMAZ (_bgFinish) */
@@ -8505,34 +8543,42 @@ function finAgentRegisterBg(s, symbols) {
8505
8543
  fixes: 0,
8506
8544
  endedAt: null,
8507
8545
  error: null,
8508
- });
8546
+ };
8547
+ engine._bgJobs.set(String(s.id), job);
8509
8548
  engine._bgEmit();
8549
+ /* otomatik ekip grubuna katıl: grup thread'i ilk ajanla kurulur,
8550
+ sonraki her finance ajanı aynı gruba eklenir (katılım postu düşer) */
8551
+ engine._agentTeamJoin(job);
8510
8552
  } catch {}
8511
8553
  }
8512
8554
 
8513
- function finApplyTraderFields(s, symbolsOverride) {
8555
+ function finApplyTraderFields(s, symbolsOverride, role) {
8514
8556
  const f = finCfg();
8557
+ const r = String(role || s.financeRole || '');
8515
8558
  s.finance = true;
8516
8559
  s.financeTrader = true;
8517
- s.financeAuto = f.allowTrading === true;
8560
+ s.financeRole = r;
8561
+ /* rol ajanları ASLA işlem açmaz — trade ajanı ve işçileri DAIMA açabilir */
8562
+ s.financeAuto = !r;
8518
8563
  s.financeSymbols = Array.isArray(symbolsOverride) && symbolsOverride.length ? symbolsOverride : f.symbols;
8519
8564
  s.financeStrategy = String(f.strategy || '');
8520
8565
  s.financeLimits = { maxLot: f.maxLot, maxPositions: f.maxPositions };
8521
8566
  engine.cache.set(String(s.id), s);
8522
8567
  }
8523
8568
 
8524
- /* Yeni finance ajanı aç (ana trader YA DA sembol işçisi) */
8525
- function finAgentCreate(symbols, isMain) {
8569
+ /* Yeni finance ajanı aç (ana trader YA DA sembol işçisi YA DA analiz ekibi rolü) */
8570
+ function finAgentCreate(symbols, isMain, role) {
8526
8571
  const f = finCfg();
8527
- const s = finAgentSession(symbols, isMain);
8572
+ const s = finAgentSession(symbols, isMain, role);
8528
8573
  const record = {
8529
8574
  symbols: symbols && symbols.length ? symbols.slice(0, 10) : [...(f.symbols || [])],
8530
8575
  main: !!isMain,
8576
+ role: String(role || ''),
8531
8577
  round: 0,
8532
8578
  timer: null,
8533
8579
  };
8534
8580
  financeState.agents.set(String(s.id), record);
8535
- finApplyTraderFields(s, record.symbols);
8581
+ finApplyTraderFields(s, record.symbols, record.role);
8536
8582
  finAgentRegisterBg(s, record.symbols);
8537
8583
  return { s, agent: record };
8538
8584
  }
@@ -8566,18 +8612,22 @@ function finAgentStop(sid, reason) {
8566
8612
  function finTraderBrief(agent) {
8567
8613
  const f = finCfg();
8568
8614
  const syms = agent && agent.symbols && agent.symbols.length ? agent.symbols.join(', ') : (f.symbols || []).join(', ');
8569
- const who = agent && agent.main ? 'TRADER' : 'FINANCE AJANI';
8615
+ const roleDef = finRoleDef(agent && agent.role);
8616
+ const who = agent && agent.main ? 'TRADER' : roleDef ? roleDef.label.toUpperCase() : 'FINANCE AJANI';
8570
8617
  return [
8571
8618
  `Beast Finance ${who} başlatıldı — ilk tur: strateji çerçeveni kur ve piyasa taramasını yap.`,
8572
8619
  `Odak semboller: ${syms || '(boş — mt5_status ile terminale bak, mantıklı semboller seç)'}`,
8573
8620
  `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.',
8621
+ roleDef
8622
+ ? `UZMANLIK: ${roleDef.desc} — raporlarını bu çerçevede yaz; İŞLEM AÇMA, yalnız analiz + net öneri üret.`
8623
+ : 'Otomatik işlem AÇIK (daima): mt5_trade/mt5_close/mt5_modify/mt5_pending kullanabilirsin (limitler sistemce zorlanır).',
8624
+ roleDef
8625
+ ? '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.'
8626
+ : f.strategy ? `Sahibinin strateji notu: ${f.strategy}` : 'Strateji notu yok: trend + destek/direnç + momentum ile temel okuma yap.',
8627
+ !roleDef && f.strategy ? `Sahibinin strateji notu: ${f.strategy}` : '',
8578
8628
  'Bu turda: mt5_status → hesap/pozisyon/fiyat verisi → değerlendirme → kararlar (veya BEKLE: sebep) → kısa rapor.',
8579
8629
  'Diğer finance/paralel ajanlarla koordinasyon için agent_dm aracı var (to: ajan başlığı anahtar kelimesi).',
8580
- ].join('\n');
8630
+ ].filter(Boolean).join('\n');
8581
8631
  }
8582
8632
 
8583
8633
  /* Her tur öncesi CHAT AJANINDAN plan iste: aynı motorun odaklı alt-ajanı
@@ -8597,7 +8647,7 @@ async function finConsultPlan(f, agent, sid) {
8597
8647
  if (ps && ps.ok) positions = (ps.data && ps.data.positions) || [];
8598
8648
  }
8599
8649
  } catch {}
8600
- const auto = f.allowTrading ? 'AÇIK (işlem açabilir)' : 'KAPALI (yalnız öneri)';
8650
+ const auto = 'AÇIK (işlem açabilir — limitler zorlanır)';
8601
8651
  const task =
8602
8652
  'Beast Finance ajanı yeni tura giriyor. SEN mt5_* araçlarını KULLANMA, işlem AÇMA — ' +
8603
8653
  'sana düşen iş: semboller için bu turun kısa işlem PLANINI üretmek ' +
@@ -8643,15 +8693,16 @@ function finAgentRound(sid) {
8643
8693
  agent.round += 1;
8644
8694
  if (agent.main) financeState.traderRounds = agent.round;
8645
8695
  const f = finCfg();
8696
+ const roleDef = finRoleDef(agent.role);
8646
8697
  try {
8647
8698
  const s = engine.cache.get(String(sid));
8648
- if (s) finApplyTraderFields(s, agent.symbols);
8699
+ if (s) finApplyTraderFields(s, agent.symbols, agent.role);
8649
8700
  } 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.';
8701
+ const auto = roleDef
8702
+ ? `UZMANLIK: ${roleDef.desc} — İŞLEM AÇMA, yalnız analiz + net öneri.`
8703
+ : 'İşlem açabilirsin — limitlere uy, SL\u2019siz pozisyon bırakma.';
8653
8704
  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.`;
8705
+ 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
8706
  const launch = (planBlock) => {
8656
8707
  if (!financeState.agents.has(String(sid))) return;
8657
8708
  const ok = engine.send(sid, planBlock + round, { userAction: false });
@@ -8664,8 +8715,8 @@ function finAgentRound(sid) {
8664
8715
  agent.timer = setTimeout(() => { try { finAgentRound(sid); } catch {} }, 30000);
8665
8716
  }
8666
8717
  };
8667
- if (f.consultChat === false) {
8668
- /* danışma kapalı: tur doğrudan başlar */
8718
+ if (f.consultChat === false || roleDef) {
8719
+ /* danışma kapalı ya da rol ajanı (plan trader'a yöneliktir): tur doğrudan başlar */
8669
8720
  launch('');
8670
8721
  return;
8671
8722
  }
@@ -8703,6 +8754,7 @@ ipcMain.handle('finance:state', async () => {
8703
8754
  ok: true,
8704
8755
  mode: financeState.mode,
8705
8756
  cfg: f,
8757
+ roles: FIN_ROLES,
8706
8758
  bridge: mt5bridge.status(),
8707
8759
  trader: { on: financeState.traderOn, busy, rounds: financeState.traderRounds, lastAt: financeState.lastRoundAt, sid: financeState.traderSid },
8708
8760
  models: engine ? engine.publicState().models : [],
@@ -8714,15 +8766,18 @@ ipcMain.handle('finance:snapshot', async () => {
8714
8766
  const st = financeEnsureBridge();
8715
8767
  let account = st.account || null;
8716
8768
  let positions = [];
8769
+ let orders = [];
8717
8770
  let symbols = [];
8718
8771
  if (st.running) {
8719
- const [a, p, sy] = await Promise.all([
8772
+ const [a, p, o, sy] = await Promise.all([
8720
8773
  mt5bridge.call('account', {}, 8000).catch(() => null),
8721
8774
  mt5bridge.call('positions', {}, 8000).catch(() => null),
8775
+ mt5bridge.call('orders', {}, 8000).catch(() => null),
8722
8776
  mt5bridge.call('symbols', { symbols: f.symbols || [] }, 10000).catch(() => null),
8723
8777
  ]);
8724
8778
  if (a && a.ok) account = a.data && a.data.account;
8725
8779
  if (p && p.ok) positions = (p.data && p.data.positions) || [];
8780
+ if (o && o.ok) orders = (o.data && o.data.orders) || [];
8726
8781
  if (sy && sy.ok) symbols = (sy.data && sy.data.symbols) || [];
8727
8782
  }
8728
8783
  const busy = financeState.traderSid && engine ? engine.isBusy(financeState.traderSid) : false;
@@ -8731,8 +8786,10 @@ ipcMain.handle('finance:snapshot', async () => {
8731
8786
  bridge: { running: st.running, connected: st.connected, error: st.error, terminal: st.terminal, python: st.python },
8732
8787
  account,
8733
8788
  positions,
8789
+ orders,
8734
8790
  symbols,
8735
8791
  cfg: f,
8792
+ roles: FIN_ROLES,
8736
8793
  trader: { on: financeState.traderOn, busy, rounds: financeState.traderRounds, lastAt: financeState.lastRoundAt },
8737
8794
  };
8738
8795
  });
@@ -8764,15 +8821,8 @@ ipcMain.handle('finance:mode', async (_e, payload) => {
8764
8821
  }
8765
8822
  } catch {}
8766
8823
  }
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
- }
8824
+ /* OTOMATİK TRADER KAPALI: finance moduna geçmek trader'ı KENDİLİĞİNDEN
8825
+ başlatmaz — kullanıcı TRADE AJANI kartındaki ▶ ile elle başlatır */
8776
8826
  return { ok: true, mode: financeState.mode, needNew };
8777
8827
  });
8778
8828
 
@@ -8802,8 +8852,11 @@ ipcMain.handle('finance:settings', async (_e, patch) => {
8802
8852
  if (p.intervalSec !== undefined) f.intervalSec = Math.max(30, Math.min(3600, Math.round(Number(p.intervalSec) || 120)));
8803
8853
  if (p.maxLot !== undefined) f.maxLot = Math.max(0.01, Math.min(100, Number(p.maxLot) || 0.1));
8804
8854
  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;
8855
+ /* allowTrading artık AYARLANMAZ — daima true (finCfg zorlar) */
8806
8856
  if (p.strategy !== undefined) f.strategy = String(p.strategy || '').slice(0, 2000);
8857
+ if (p.analysisTeam !== undefined) f.analysisTeam = finRolesValid(p.analysisTeam);
8858
+ if (p.analysisAuto !== undefined) f.analysisAuto = !!p.analysisAuto;
8859
+ if (p.analysisCount !== undefined) f.analysisCount = Math.max(0, Math.min(FIN_ROLES_AUTO.length, Math.round(Number(p.analysisCount) || 0)));
8807
8860
  if (p.pythonPath !== undefined) f.pythonPath = String(p.pythonPath || '').trim();
8808
8861
  if (p.terminalPath !== undefined) f.terminalPath = String(p.terminalPath || '').trim();
8809
8862
  if (p.traderSel !== undefined) {
@@ -8860,24 +8913,58 @@ async function financeTraderStart() {
8860
8913
  financeState.lastRoundAt = Date.now();
8861
8914
  financeLog('[trader] başlatıldı (model: ' + (f.traderSel || 'genel aktif model') + ')');
8862
8915
  finPush('trader', { state: 'running', round: mainAgent.round });
8916
+ /* ANALİZ EKİBİ: seçili roller için ayrı sürekli ajanlar (koşmıyorsa) */
8917
+ finTeamStart(f);
8863
8918
  return { ok: true, sid: mainSid };
8864
8919
  }
8865
8920
 
8921
+ /* ANALİZ EKİBİ: seçili her rol için AYRI sürekli finance ajanı — hepsi
8922
+ tüm SKILL'lere + mt5 okuma araçlarına erişir, İŞLEM AÇMAZ.
8923
+ Otomatik mod: kullanıcı sadece SAYI girer → gereklı ajanlar atanır. */
8924
+ function finTeamStart(f) {
8925
+ const n = Math.max(0, Math.min(FIN_ROLES_AUTO.length, Math.round(Number(f && f.analysisCount) || 0)));
8926
+ const roles = f && f.analysisAuto
8927
+ ? FIN_ROLES_AUTO.slice(0, n)
8928
+ : finRolesValid((f && f.analysisTeam) || []);
8929
+ if (!roles.length) return;
8930
+ const running = new Set();
8931
+ for (const [, a] of financeState.agents) {
8932
+ if (a.role) running.add(a.role);
8933
+ }
8934
+ for (const roleId of roles) {
8935
+ if (running.has(roleId)) continue;
8936
+ const roleDef = finRoleDef(roleId);
8937
+ try {
8938
+ const created = finAgentCreate([], false, roleId);
8939
+ try { engine.setSessionModel(created.s.id, f.traderSel || null); } catch {}
8940
+ const ok = engine.send(created.s.id, finTraderBrief(created.agent), { userAction: true });
8941
+ if (!ok) {
8942
+ finAgentStop(String(created.s.id), 'oturum meşgul — ekip ajanı başlatılamadı');
8943
+ financeLog('[ekip] ' + roleDef.label + ' başlatılamadı: oturum meşgul');
8944
+ } else {
8945
+ financeLog('[ekip] ' + roleDef.label + ' başladı — analiz turu koşuyor (İşlem AÇMAZ)');
8946
+ }
8947
+ } catch (e) {
8948
+ financeLog('[ekip] ' + roleDef.label + ' hatası: ' + String((e && e.message) || e));
8949
+ }
8950
+ }
8951
+ }
8952
+
8866
8953
  ipcMain.handle('finance:trader:start', () => financeTraderStart());
8867
8954
 
8868
8955
  ipcMain.handle('finance:trader:stop', async () => {
8869
8956
  financeState.traderOn = false;
8870
- /* ANA trader'ı durdur (sembol işçileri rail × ile ayrı durdurulur) */
8957
+ /* ANA trader + ANALİZ EKİBİ durdurulur (sembol işçileri rail × ile ayrı durdurulur) */
8871
8958
  let mainSid = '';
8959
+ const teamSids = [];
8872
8960
  for (const [sid, a] of financeState.agents) {
8873
- if (a.main) {
8874
- mainSid = sid;
8875
- break;
8876
- }
8961
+ if (a.main) mainSid = sid;
8962
+ else if (a.role) teamSids.push(sid);
8877
8963
  }
8878
8964
  const stopped = mainSid ? finAgentStop(mainSid, 'kullanıcı Beast Finance trader ajanını durdurdu') : false;
8965
+ for (const sid of teamSids) finAgentStop(sid, 'kullanıcı Beast Finance ekip ajanını durdurdu');
8879
8966
  const interrupted = !!stopped;
8880
- financeLog('[trader] durduruldu');
8967
+ financeLog('[trader] durduruldu' + (teamSids.length ? ' (ekip: ' + teamSids.length + ' ajan)' : ''));
8881
8968
  finPush('trader', { state: 'stopped' });
8882
8969
  return { ok: true, interrupted };
8883
8970
  });
@@ -8924,6 +9011,19 @@ ipcMain.handle('finance:close', async (_e, payload) => {
8924
9011
  return r;
8925
9012
  });
8926
9013
 
9014
+ /* Bekleyen emri panelden iptal et (ajanın mt5_cancel aracıyla aynı köprü) */
9015
+ ipcMain.handle('finance:cancel', async (_e, payload) => {
9016
+ const ticket = Number(payload && payload.ticket);
9017
+ if (!ticket) return { ok: false, error: 'ticket gerekli' };
9018
+ if (!mt5bridge.running) return { ok: false, error: 'MT5 köprüsü bağlı değil' };
9019
+ const r = await mt5bridge.call('cancel', { ticket }, 20000);
9020
+ if (r && r.ok) {
9021
+ financeLog('[panel] bekleyen emir iptal edildi: ticket ' + ticket);
9022
+ finPush('trade', { line: 'EMİR İPTAL (panel): ticket ' + ticket });
9023
+ }
9024
+ return r;
9025
+ });
9026
+
8927
9027
  ipcMain.handle('finance:install', async () => {
8928
9028
  financeState.installTried = false;
8929
9029
  financeTryInstall();
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),
@@ -116,6 +117,7 @@ contextBridge.exposeInMainWorld('beast', {
116
117
  financeAgentSpawn: (symbol) => ipcRenderer.invoke('finance:agent:spawn', { symbol }),
117
118
  financeConnect: () => ipcRenderer.invoke('finance:connect'),
118
119
  financeClose: (ticket, volume) => ipcRenderer.invoke('finance:close', { ticket, volume }),
120
+ financeCancel: (ticket) => ipcRenderer.invoke('finance:cancel', { ticket }),
119
121
  financeInstall: () => ipcRenderer.invoke('finance:install'),
120
122
  financeSymbolsList: (filter) => ipcRenderer.invoke('finance:symbols:list', filter),
121
123
  clipRead: () => ipcRenderer.invoke('clip:read'),
@@ -161,7 +161,7 @@
161
161
  <div class="fin-kv"><span>Serbest Marj</span><b id="finFree">—</b></div>
162
162
  </div>
163
163
  </div>
164
- <div class="fin-card">
164
+ <div class="fin-card fin-card-trader">
165
165
  <div class="fin-card-title">TRADE AJANI <span id="finTraderDot" class="fin-dot off" title="Trader durumu"></span></div>
166
166
  <div class="fin-row"><select id="finTraderModel" title="Trader ajanının modeli — farklı API/model seçilebilir"></select></div>
167
167
  <div class="fin-row fin-mini">
@@ -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">
173
- <label class="fin-check"><input id="finAllow" type="checkbox" /> Otomatik işlem (ajan trade açabilir)</label>
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>
176
+ <div class="fin-row fin-btn-row">
174
177
  <button id="finTraderBtn" class="fin-start">&#9654;&#xFE0E; Ajanı Başlat</button>
175
178
  </div>
176
179
  <div id="finTraderStatus">Kapalı</div>
@@ -179,6 +182,10 @@
179
182
  <div class="fin-card-title">POZİSYONLAR <span id="finPosCount"></span></div>
180
183
  <div id="finPosList"><div class="fin-empty">Açık pozisyon yok</div></div>
181
184
  </div>
185
+ <div class="fin-card">
186
+ <div class="fin-card-title">BEKLEYEN EMİRLER <span id="finOrdCount"></span></div>
187
+ <div id="finOrdList"><div class="fin-empty">Bekleyen emir yok</div></div>
188
+ </div>
182
189
  <div class="fin-card">
183
190
  <div class="fin-card-title">
184
191
  PİYASA
@@ -380,6 +387,7 @@
380
387
  <span class="bot-bar-btns">
381
388
  <button id="dmBackBtn" title="Oturum listesine dön" hidden>&#x25C0;&#xFE0E; Oturumlar</button>
382
389
  <button id="dmColumnBtn" title="Yan sütunda canlı akış olarak da göster">&#x25A4;&#xFE0E;</button>
390
+ <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
391
  <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
392
  <button id="dmModalClose" title="Kapat">&#x2715;&#xFE0E;</button>
385
393
  </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,13 +173,15 @@ 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'),
180
181
  finPosList: $('#finPosList'),
181
182
  finPosCount: $('#finPosCount'),
183
+ finOrdList: $('#finOrdList'),
184
+ finOrdCount: $('#finOrdCount'),
182
185
  finSymList: $('#finSymList'),
183
186
  finLog: $('#finLog'),
184
187
  finSymAdd: $('#finSymAdd'),
@@ -5035,6 +5038,7 @@ const dmState = {
5035
5038
  groups: [], // { id, title, members, titles, createdAt, closed, closedAt }
5036
5039
  modalThread: null, // modalde açık oturum key'i (null = oturum listesi)
5037
5040
  };
5041
+ let dmGroupSyncTimer = null;
5038
5042
 
5039
5043
  function dmGroupOf(id) {
5040
5044
  return (dmState.groups || []).find((g) => String(g.id) === String(id)) || null;
@@ -5058,13 +5062,24 @@ function dmThreadClosed(msgs) {
5058
5062
  return !!last.closed;
5059
5063
  }
5060
5064
 
5065
+ /* ajan adının yanına KOD (oturum id) — kimin kim olduğunu netleştirir */
5066
+ function dmCode(code) {
5067
+ const c = String(code || '').trim();
5068
+ if (!c) return '';
5069
+ return `<span class="dm-code">${escapeHtml(c.length > 14 ? c.slice(0, 14) + '…' : c)}</span>`;
5070
+ }
5071
+
5072
+ function dmName(title, code) {
5073
+ return `${escapeHtml(String(title || 'Ajan'))}${dmCode(code)}`;
5074
+ }
5075
+
5061
5076
  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;
5077
+ if (dm.group) return '👥 ' + escapeHtml(String(dm.groupTitle || 'Grup'));
5078
+ const a = dmName(dm.fromTitle, dm.from);
5079
+ const b = dmName(dm.toTitle, dm.to);
5080
+ const pair = a + ' ↔ ' + b;
5066
5081
  const topic = String(dm.topic || '(genel)');
5067
- return topic === '(genel)' ? pair : pair + ' · ' + topic;
5082
+ return topic === '(genel)' ? pair : pair + ' · ' + escapeHtml(topic);
5068
5083
  }
5069
5084
 
5070
5085
  function dmThreads() {
@@ -5087,8 +5102,8 @@ function dmTime(iso) {
5087
5102
 
5088
5103
  function dmMsgHtml(m) {
5089
5104
  const who = m.group
5090
- ? escapeHtml(String(m.fromTitle || 'Ajan'))
5091
- : `${escapeHtml(String(m.fromTitle || 'Ajan'))} → ${escapeHtml(String(m.toTitle || 'Ajan'))}`;
5105
+ ? dmName(m.fromTitle, m.from)
5106
+ : `${dmName(m.fromTitle, m.from)} → ${dmName(m.toTitle, m.to)}`;
5092
5107
  return (
5093
5108
  `<div class="dmt-msg"><span class="dm-who">${who}</span>` +
5094
5109
  `${escapeHtml(String(m.text || ''))}<span class="dm-at">${dmTime(m.at)}</span></div>`
@@ -5106,17 +5121,20 @@ function dmThreadCard(key, msgs, onClick) {
5106
5121
  if (last.group) {
5107
5122
  const g = dmGroupOf(last.group);
5108
5123
  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>`;
5124
+ const names = g.members.slice(0, 4).map((m) => dmName((g.titles && g.titles[m]) || m, m));
5125
+ membersLine = `<div class="dmt-members">👥 ${names.join(', ')}</div>`;
5111
5126
  }
5112
5127
  }
5113
5128
  el.innerHTML =
5114
5129
  '<div class="dmt-head">' +
5115
- `<span class="dmt-title">${escapeHtml(dmThreadTitle(last))}</span>` +
5130
+ `<span class="dmt-title">${dmThreadTitle(last)}</span>` +
5116
5131
  `<span class="dmt-count">${closed ? 'kapandı' : msgs.length}</span>` +
5132
+ `<button class="dmt-del" title="Bu oturumu sil">&#x2715;&#xFE0E;</button>` +
5117
5133
  '</div>' +
5118
5134
  membersLine +
5119
5135
  `<div class="dmt-last">${escapeHtml(String(last.text || '').replace(/\s+/g, ' ').slice(0, 90))} · ${dmTime(last.at)}</div>`;
5136
+ const del = el.querySelector('.dmt-del');
5137
+ if (del) del.addEventListener('click', (ev) => { ev.stopPropagation(); dmDeleteThread(key); });
5120
5138
  el.addEventListener('click', onClick);
5121
5139
  return el;
5122
5140
  }
@@ -5182,6 +5200,7 @@ function renderDmModal() {
5182
5200
  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
5201
  if (els.dmDialogTitle) els.dmDialogTitle.textContent = 'AJAN DM — OTURUMLAR';
5184
5202
  if (els.dmBackBtn) els.dmBackBtn.hidden = true;
5203
+ if (els.dmModalDel) els.dmModalDel.hidden = true;
5185
5204
  return;
5186
5205
  }
5187
5206
  if (dmState.modalThread) {
@@ -5191,8 +5210,9 @@ function renderDmModal() {
5191
5210
  dmState.modalThread = null;
5192
5211
  return renderDmModal();
5193
5212
  }
5194
- if (els.dmDialogTitle) els.dmDialogTitle.textContent = dmThreadTitle(msgs[msgs.length - 1]);
5213
+ if (els.dmDialogTitle) els.dmDialogTitle.innerHTML = dmThreadTitle(msgs[msgs.length - 1]);
5195
5214
  if (els.dmBackBtn) els.dmBackBtn.hidden = false;
5215
+ if (els.dmModalDel) els.dmModalDel.hidden = false;
5196
5216
  list.innerHTML = '<div class="dm-thread-msgs">' + msgs.map(dmMsgHtml).join('') + '</div>';
5197
5217
  const box = list.querySelector('.dm-thread-msgs');
5198
5218
  if (box) box.scrollTop = box.scrollHeight;
@@ -5201,6 +5221,7 @@ function renderDmModal() {
5201
5221
  /* OTURUM LİSTESİ — AKTİF + GEÇMİŞ bölümleri */
5202
5222
  if (els.dmDialogTitle) els.dmDialogTitle.textContent = 'AJAN DM — AKTİF & GEÇMİŞ';
5203
5223
  if (els.dmBackBtn) els.dmBackBtn.hidden = true;
5224
+ if (els.dmModalDel) els.dmModalDel.hidden = true;
5204
5225
  list.innerHTML = '';
5205
5226
  dmAppendThreadSections(list, (key) => {
5206
5227
  dmState.modalThread = key;
@@ -5230,6 +5251,22 @@ async function dmClearAll() {
5230
5251
  toast('Ajan DM geçmişi silindi');
5231
5252
  }
5232
5253
 
5254
+ /* TEK OTURUM silme: yalnız bu sohbetin (ajan çifti + konu / grup) DM'leri gider */
5255
+ async function dmDeleteThread(key) {
5256
+ const msgs = (dmThreads().find(([k]) => k === key) || [null, []])[1];
5257
+ const last = msgs[msgs.length - 1];
5258
+ const label = last
5259
+ ? last.group
5260
+ ? String(last.groupTitle || 'Grup')
5261
+ : `${last.fromTitle || 'Ajan'} ↔ ${last.toTitle || 'Ajan'}`
5262
+ : 'bu oturum';
5263
+ if (!(await uiConfirm(`"${label}" oturumu silinsin mi?`, 'Sil'))) return;
5264
+ try { await beast.agentDmDeleteThread(key); } catch {}
5265
+ if (dmState.modalThread === key) dmState.modalThread = null;
5266
+ await refreshDmRail();
5267
+ toast('Oturum silindi');
5268
+ }
5269
+
5233
5270
  if (els.dmBtn) {
5234
5271
  els.dmBtn.addEventListener('click', () => {
5235
5272
  /* kapalıyken (dm-hidden VAR) aç: toggle 'hide' SEMANTIĞI tersine çevrilir */
@@ -5238,6 +5275,11 @@ if (els.dmBtn) {
5238
5275
  }
5239
5276
  if (els.dmModalClose) els.dmModalClose.addEventListener('click', closeDmModal);
5240
5277
  if (els.dmModalClear) els.dmModalClear.addEventListener('click', dmClearAll);
5278
+ if (els.dmModalDel) {
5279
+ els.dmModalDel.addEventListener('click', () => {
5280
+ if (dmState.modalThread) dmDeleteThread(dmState.modalThread); // açık oturumu sil
5281
+ });
5282
+ }
5241
5283
  if (els.dmBackBtn) {
5242
5284
  els.dmBackBtn.addEventListener('click', () => {
5243
5285
  dmState.modalThread = null; // oturum listesine dön
@@ -5613,6 +5655,20 @@ function onEvent(ev) {
5613
5655
  dmState.dms.push(rec);
5614
5656
  if (dmState.dms.length > 400) dmState.dms.splice(0, dmState.dms.length - 400);
5615
5657
  }
5658
+ /* GRUP mesajı canlı geldi: grup kaydını (üyeler/başlık/kapandı) sunucudan
5659
+ tazele — kart İLK mesajla birlikte tam grup görünümünde düşer, "önce
5660
+ ayrı ayrı görünür sonra gruba geçer" durumu olmaz */
5661
+ if (rec.group) {
5662
+ clearTimeout(dmGroupSyncTimer);
5663
+ dmGroupSyncTimer = setTimeout(() => {
5664
+ beast
5665
+ .agentDmsList()
5666
+ .then((res) => {
5667
+ if (res && !Array.isArray(res)) dmState.groups = res.groups || [];
5668
+ })
5669
+ .catch(() => {});
5670
+ }, 200);
5671
+ }
5616
5672
  if (els.dmOverlay && !els.dmOverlay.hidden) renderDmModal();
5617
5673
  if ($('#dmRail') && !$('#dmRail').classList.contains('dm-hidden')) renderDmRail();
5618
5674
  return;
@@ -8820,6 +8876,56 @@ function finRenderPositions(list) {
8820
8876
  }
8821
8877
  }
8822
8878
 
8879
+ /* MT5 bekleyen emir tipi → etiket + renk sınıfı (0..7) */
8880
+ const FIN_ORDER_TYPES = {
8881
+ 0: { t: 'BUY', c: 'buy' },
8882
+ 1: { t: 'SELL', c: 'sell' },
8883
+ 2: { t: 'BUY LIMIT', c: 'buy' },
8884
+ 3: { t: 'SELL LIMIT', c: 'sell' },
8885
+ 4: { t: 'BUY STOP', c: 'buy' },
8886
+ 5: { t: 'SELL STOP', c: 'sell' },
8887
+ 6: { t: 'BUY STP-LMT', c: 'buy' },
8888
+ 7: { t: 'SELL STP-LMT', c: 'sell' },
8889
+ };
8890
+
8891
+ function finRenderOrders(list) {
8892
+ if (!els.finOrdList) return;
8893
+ els.finOrdList.textContent = '';
8894
+ if (els.finOrdCount) els.finOrdCount.textContent = list && list.length ? '(' + list.length + ')' : '';
8895
+ if (!list || !list.length) {
8896
+ const d = document.createElement('div');
8897
+ d.className = 'fin-empty';
8898
+ d.textContent = 'Bekleyen emir yok';
8899
+ els.finOrdList.appendChild(d);
8900
+ return;
8901
+ }
8902
+ for (const o of list) {
8903
+ const row = document.createElement('div');
8904
+ row.className = 'fin-pos fin-ord';
8905
+ const ot = FIN_ORDER_TYPES[Number(o.type)] || { t: String(o.type ?? '?'), c: '' };
8906
+ const vol = Number(o.volume_current) || Number(o.volume_initial) || 0;
8907
+ row.innerHTML =
8908
+ '<span class="fp-sym" title="ticket ' + o.ticket + '">' + escapeHtml(String(o.symbol || '?')) + '</span>' +
8909
+ '<span class="fp-dir ' + ot.c + '">' + ot.t + '</span>' +
8910
+ '<span class="fp-meta">' + vol + ' lot @ ' + (Number(o.price_open) || 0) +
8911
+ (o.sl ? ' · SL ' + o.sl : '') + (o.tp ? ' · TP ' + o.tp : '') + '</span>';
8912
+ const btn = document.createElement('button');
8913
+ btn.className = 'fp-close';
8914
+ btn.title = 'Bekleyen emri iptal et';
8915
+ btn.textContent = '✕';
8916
+ btn.addEventListener('click', async () => {
8917
+ const ok = await uiConfirm((o.symbol || '') + ' bekleyen emri iptal edilsin mi? (ticket ' + o.ticket + ')', 'İptal Et');
8918
+ if (!ok) return;
8919
+ const r = await beast.financeCancel(o.ticket).catch((e) => ({ ok: false, error: String((e && e.message) || e) }));
8920
+ if (r && r.ok) toast('Emir iptal edildi');
8921
+ else toast('İptal edilemedi: ' + ((r && r.error) || '?'));
8922
+ finSnapshot();
8923
+ });
8924
+ row.appendChild(btn);
8925
+ els.finOrdList.appendChild(row);
8926
+ }
8927
+ }
8928
+
8823
8929
  function finRenderSymbols(list) {
8824
8930
  if (!els.finSymList) return;
8825
8931
  els.finSymList.textContent = '';
@@ -8885,10 +8991,94 @@ function finTraderInputsSet(cfg) {
8885
8991
  if (els.finInterval && ae !== els.finInterval) els.finInterval.value = cfg.intervalSec || 120;
8886
8992
  if (els.finMaxLot && ae !== els.finMaxLot) els.finMaxLot.value = cfg.maxLot || 0.1;
8887
8993
  finSymBtnUpdate();
8888
- if (els.finAllow && ae !== els.finAllow) els.finAllow.checked = cfg.allowTrading === true;
8889
8994
  if (els.finTraderModel && ae !== els.finTraderModel && cfg.traderSel) els.finTraderModel.value = cfg.traderSel;
8890
8995
  }
8891
8996
 
8997
+ /* ANALİZ EKİBİ: trader yanında koşacak uzman ajan rolleri — İKİ MOD:
8998
+ (1) OTOMATİK: sadece SAYI gir → gereklı ajanları sistem kendisi ata;
8999
+ (2) ELLE SEÇ: rol kutucukları (Risk / Teknik / Haber-Makro) çoklu seçilir.
9000
+ Katalog sunucudan (finance snapshot/state.roles) gelir, yerel yedek var. */
9001
+ let finRolesCatalog = [
9002
+ { id: 'risk', label: 'Risk Ajanı', desc: 'marj/kaldıraç/SL disiplini, exposure ve günlük kayıp hızı denetimi' },
9003
+ { id: 'technic', label: 'Teknik Analiz', desc: 'trend/yapı/destek-direnç/momentum okuma, AL-SAT-BEKLE önerileri' },
9004
+ { id: 'macro', label: 'Haber / Makro', desc: 'haber akışı + ekonomik takvim, DXY/emtia bağıntıları, yön eğilimi' },
9005
+ ];
9006
+ let finRolesDirty = false; /* tık→kaydet arası snapshot ezmesin */
9007
+ let finRolesSyncTimer = null;
9008
+
9009
+ function finRolesSel() {
9010
+ const box = els.finRoles;
9011
+ return box ? [...box.querySelectorAll('input[data-role]:checked')].map((cb) => cb.dataset.role) : [];
9012
+ }
9013
+
9014
+ function finRenderRoles(cfg) {
9015
+ const box = els.finRoles;
9016
+ if (!box) return;
9017
+ if (finRolesDirty) return; /* bekleyen kullanıcı değişikliği korunur */
9018
+ const auto = !!(cfg && cfg.analysisAuto);
9019
+ const count = Math.max(0, Math.round(Number(cfg && cfg.analysisCount) || 0));
9020
+ const sel = cfg && Array.isArray(cfg.analysisTeam) ? cfg.analysisTeam : [];
9021
+ box.innerHTML = '';
9022
+
9023
+ /* OTOMATİK çipi + sayı girişi */
9024
+ const autoLab = document.createElement('label');
9025
+ autoLab.className = 'fin-role fin-role-auto';
9026
+ autoLab.title = 'Rol seçmeden SAYI gir — gereklı ajanları sistem otomatik atar (teknik → risk → haber sırası)';
9027
+ const autoCb = document.createElement('input');
9028
+ autoCb.type = 'checkbox';
9029
+ autoCb.checked = auto;
9030
+ autoLab.appendChild(autoCb);
9031
+ autoLab.appendChild(document.createTextNode('⚙ Otomatik'));
9032
+ autoCb.addEventListener('change', () => {
9033
+ finRolesDirty = true;
9034
+ finSaveCfg({ analysisAuto: autoCb.checked });
9035
+ toast(autoCb.checked ? 'Analiz ekibi OTOMATİK — sayı kadar gereklı ajan atanır' : 'Analiz ekibi ELLE SEÇİM moduna geçti');
9036
+ clearTimeout(finRolesSyncTimer);
9037
+ finRolesSyncTimer = setTimeout(() => { finRolesDirty = false; finSnapshot(); }, 900);
9038
+ });
9039
+ box.appendChild(autoLab);
9040
+
9041
+ const cnt = document.createElement('input');
9042
+ cnt.type = 'number';
9043
+ cnt.className = 'fin-role-count';
9044
+ cnt.min = '0';
9045
+ cnt.max = String(finRolesCatalog.length);
9046
+ cnt.step = '1';
9047
+ cnt.value = String(count || 0);
9048
+ cnt.title = 'Otomatik mod: kaç uzman ajan koşacak (0 = ekip yok)';
9049
+ cnt.disabled = !auto;
9050
+ cnt.addEventListener('change', () => {
9051
+ finRolesDirty = true;
9052
+ finSaveCfg({ analysisCount: Math.max(0, Math.round(Number(cnt.value) || 0)) });
9053
+ toast('Analiz ekibi: ' + (Math.round(Number(cnt.value) || 0) || 'kapalı') + ' uzman ajan (otomatik)');
9054
+ clearTimeout(finRolesSyncTimer);
9055
+ finRolesSyncTimer = setTimeout(() => { finRolesDirty = false; finSnapshot(); }, 900);
9056
+ });
9057
+ box.appendChild(cnt);
9058
+
9059
+ /* rol kutucukları — otomatik moddayken pasif */
9060
+ for (const r of finRolesCatalog) {
9061
+ const lab = document.createElement('label');
9062
+ lab.className = 'fin-role';
9063
+ const cb = document.createElement('input');
9064
+ cb.type = 'checkbox';
9065
+ cb.dataset.role = r.id;
9066
+ cb.checked = sel.includes(r.id);
9067
+ cb.disabled = auto;
9068
+ cb.title = r.desc + ' — tüm skill + mt5 erişimli, işlem açmaz';
9069
+ lab.appendChild(cb);
9070
+ lab.appendChild(document.createTextNode(r.label));
9071
+ cb.addEventListener('change', () => {
9072
+ finRolesDirty = true;
9073
+ finSaveCfg({ analysisTeam: finRolesSel() });
9074
+ toast(r.label + (cb.checked ? ' ekibe eklendi — sonraki başlatışta koşar' : ' ekipten çıkarıldı'));
9075
+ clearTimeout(finRolesSyncTimer);
9076
+ finRolesSyncTimer = setTimeout(() => { finRolesDirty = false; finSnapshot(); }, 900);
9077
+ });
9078
+ box.appendChild(lab);
9079
+ }
9080
+ }
9081
+
8892
9082
  function finRenderTrader(trader, cfg) {
8893
9083
  if (!els.finTraderBtn) return;
8894
9084
  const on = !!(trader && trader.on);
@@ -8900,15 +9090,21 @@ function finRenderTrader(trader, cfg) {
8900
9090
  else els.finTraderDot.classList.add('off');
8901
9091
  }
8902
9092
  if (els.finTraderStatus) {
9093
+ els.finTraderStatus.classList.toggle('on', on);
9094
+ els.finTraderStatus.classList.toggle('busy', !!(on && trader && trader.busy));
8903
9095
  let t = '';
8904
9096
  if (trader && trader.lastAt) {
8905
9097
  const d = new Date(trader.lastAt);
8906
9098
  t = ' · son tur ' + String(d.getHours()).padStart(2, '0') + ':' + String(d.getMinutes()).padStart(2, '0');
8907
9099
  }
8908
- const mode = cfg && cfg.allowTrading ? 'OTOMATİK' : 'analiz modu';
9100
+ /* ekip bilgisi: otomatik modda sayı, elle seçimde seçili rol sayısı */
9101
+ const team = cfg && cfg.analysisAuto
9102
+ ? Math.round(Number(cfg.analysisCount) || 0)
9103
+ : (cfg && Array.isArray(cfg.analysisTeam) ? cfg.analysisTeam.length : 0);
9104
+ const teamTxt = team ? ' · ekip ' + team + ' ajan' + (cfg && cfg.analysisAuto ? ' (oto)' : '') : '';
8909
9105
  els.finTraderStatus.textContent = on
8910
- ? (trader.busy ? 'Tur çalışıyor…' : 'Beklemede — sıradaki tur ' + (cfg && cfg.intervalSec ? cfg.intervalSec + ' sn' : '')) + ' · tur #' + (trader.rounds || 0) + ' · ' + mode + t
8911
- : 'Kapalı · ' + mode;
9106
+ ? (trader.busy ? 'Tur çalışıyor…' : 'Beklemede — sıradaki tur ' + (cfg && cfg.intervalSec ? cfg.intervalSec + ' sn' : '')) + ' · tur #' + (trader.rounds || 0) + ' · OTONOM' + teamTxt + t
9107
+ : 'Kapalı · OTONOM' + teamTxt;
8912
9108
  }
8913
9109
  }
8914
9110
 
@@ -8945,8 +9141,11 @@ async function finSnapshot() {
8945
9141
  finSetDot(b.connected ? true : b.running ? 'busy' : false, b);
8946
9142
  finRenderAccount(r.account);
8947
9143
  finRenderPositions(r.positions || []);
9144
+ finRenderOrders(r.orders || []);
8948
9145
  finRenderSymbols(r.symbols || []);
9146
+ if (Array.isArray(r.roles) && r.roles.length) finRolesCatalog = r.roles;
8949
9147
  finTraderInputsSet(r.cfg);
9148
+ finRenderRoles(r.cfg);
8950
9149
  finRenderTrader(r.trader, r.cfg);
8951
9150
  }
8952
9151
 
@@ -9042,22 +9241,7 @@ function finSaveCfg(patch) {
9042
9241
  if (els.finInterval) els.finInterval.addEventListener('change', () => finSaveCfg({ intervalSec: Number(els.finInterval.value) || 120 }));
9043
9242
  if (els.finMaxLot) els.finMaxLot.addEventListener('change', () => finSaveCfg({ maxLot: Number(els.finMaxLot.value) || 0.1 }));
9044
9243
  if (els.finSymBtn) els.finSymBtn.addEventListener('click', finSymPickerOpen);
9045
- if (els.finAllow) {
9046
- els.finAllow.addEventListener('change', async () => {
9047
- if (els.finAllow.checked) {
9048
- const ok = await uiConfirm(
9049
- 'OTOMATİK İŞLEM açılıyor: trade ajanı GERÇEK hesapta emir açabilir/kapatabilir (max lot + pozisyon limitleriyle). Onaylıyor musun?',
9050
- 'Aç',
9051
- 'Vazgeç'
9052
- );
9053
- if (!ok) {
9054
- els.finAllow.checked = false;
9055
- return;
9056
- }
9057
- }
9058
- finSaveCfg({ allowTrading: els.finAllow.checked });
9059
- });
9060
- }
9244
+ /* OTOMATİK İŞLEM onay kutusu KALDIRILDI — trade ajanı daima işlem açabilir */
9061
9245
  if (els.finTraderModel) {
9062
9246
  els.finTraderModel.addEventListener('change', () => {
9063
9247
  finSaveCfg({ traderSel: els.finTraderModel.value });
@@ -9071,7 +9255,14 @@ if (els.finTraderBtn) {
9071
9255
  } else {
9072
9256
  const r = await beast.financeTraderStart().catch((e) => ({ ok: false, error: String((e && e.message) || e) }));
9073
9257
  if (r && !r.ok) toast(r.error || 'Trader başlatılamadı');
9074
- else if (r && r.ok) toast('Trade ajanı başladı — turlar panelde izlenir');
9258
+ else if (r && r.ok) {
9259
+ const cfg = (await beast.financeState().catch(() => null));
9260
+ const c = (cfg && cfg.cfg) || {};
9261
+ const team = c.analysisAuto
9262
+ ? Math.round(Number(c.analysisCount) || 0)
9263
+ : finRolesSel().length;
9264
+ toast(team ? 'Trade ajanı + analiz ekibi (' + team + ' uzman) başladı — turlar panelde izlenir' : 'Trade ajanı başladı — turlar panelde izlenir');
9265
+ }
9075
9266
  }
9076
9267
  finSnapshot();
9077
9268
  });
@@ -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,30 +4864,91 @@ 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 */
4920
+ }
4921
+
4922
+ /* TRADE AJANI kartı — vurgulu görünüm (accent çerçeve + yumuşak zemin) */
4923
+ .fin-card-trader {
4924
+ border-color: var(--accent);
4925
+ box-shadow: 0 0 0 1px var(--accent-dim), 0 2px 10px rgba(0, 0, 0, 0.1);
4926
+ background: linear-gradient(180deg, var(--accent-dim), transparent 46%), var(--panel);
4858
4927
  }
4859
- .fin-start:hover { filter: brightness(1.1); }
4860
- .fin-start.stop { background: #ef4444; }
4928
+ .fin-card-trader .fin-card-title { color: var(--text); }
4929
+ .fin-card-trader .fin-row { margin-bottom: 8px; }
4930
+ .fin-card-trader .fin-btn-row { margin-top: 2px; }
4931
+ .fin-btn-row .fin-start { flex: 1; width: 100%; padding: 7px 14px; }
4861
4932
 
4862
4933
  #finTraderStatus {
4863
- margin-top: 7px;
4864
- font-size: 11px;
4934
+ margin-top: 8px;
4935
+ font-size: 10.5px;
4865
4936
  color: var(--muted);
4937
+ background: var(--panel2);
4938
+ border: 1px solid var(--border);
4939
+ border-radius: 7px;
4940
+ padding: 5px 8px;
4866
4941
  white-space: nowrap;
4867
4942
  overflow: hidden;
4868
4943
  text-overflow: ellipsis;
4869
4944
  }
4945
+ #finTraderStatus::before { content: '● '; font-size: 9px; color: var(--muted); }
4946
+ #finTraderStatus.on { color: var(--text); border-color: var(--accent); }
4947
+ #finTraderStatus.on::before { color: #22c55e; }
4948
+ #finTraderStatus.on.busy { color: var(--accent); }
4949
+ #finTraderStatus.on.busy::before { color: #f59e0b; }
4870
4950
 
4871
- #finPosList, #finSymList { display: flex; flex-direction: column; gap: 5px; }
4951
+ #finPosList, #finOrdList, #finSymList { display: flex; flex-direction: column; gap: 5px; }
4872
4952
  .fin-empty { font-size: 11px; color: var(--muted); padding: 2px 0 4px; }
4873
4953
 
4874
4954
  .fin-pos {
@@ -4907,6 +4987,8 @@ body.finance-mode #composerWrap { margin-right: var(--finW, 400px); }
4907
4987
  border-radius: 5px;
4908
4988
  }
4909
4989
  .fin-pos .fp-close:hover { background: rgba(239, 68, 68, 0.15); color: #ef4444; }
4990
+ /* bekleyen emir satırı: amber sol şerit ile pozisyondan ayrışır */
4991
+ .fin-pos.fin-ord { border-left: 2px solid #f59e0b; }
4910
4992
 
4911
4993
  .fin-sym {
4912
4994
  display: flex;