beast-agent 2.18.0 → 2.19.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.18.0",
4
+ "version": "2.19.0",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
@@ -359,6 +359,16 @@ class Engine {
359
359
  /* #17 kalıcı ajan geçmişi: restart sonrası işler + sohbetler kaybolmasın */
360
360
  this._bgJobsFile = path.join(this.sessionsDir, 'bg-jobs.json');
361
361
  this._loadBgJobsPersisted();
362
+ /* AJAN DM: paralel/finance ajanlarının birbirine attığı mesajlar
363
+ (AJAN DM paneli buradan beslenir; restart sonrası da durur) */
364
+ this._agentDms = [];
365
+ this._agentDmsFile = path.join(this.sessionsDir, 'agent-dms.json');
366
+ try {
367
+ const raw = JSON.parse(fs.readFileSync(this._agentDmsFile, 'utf8'));
368
+ this._agentDms = Array.isArray(raw.dms) ? raw.dms : [];
369
+ } catch {
370
+ this._agentDms = [];
371
+ }
362
372
  /* superyorizyon döngüsü: takılan/uzun süren ajanı CEO'ya bildirir */
363
373
  this._supTimer = setInterval(() => {
364
374
  try { this._supervise(); } catch {}
@@ -1835,14 +1845,23 @@ class Engine {
1835
1845
  const skList = (() => {
1836
1846
  try { return skills.scan(); } catch { return []; }
1837
1847
  })();
1848
+ /* CHATTEKI BEAST KİMLİĞİ: finance ajanı da sahibin SOUL/USER/KURALLARINI taşır */
1849
+ const mem = memory.loadAll();
1850
+ const rules = memory.listRules();
1851
+ const isWorker = !!(session && session.bgJob && !isTrader);
1852
+ const modeBlock2 = isWorker
1853
+ ? '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.'
1854
+ : modeBlock;
1838
1855
  return (
1856
+ (mem.soul ? mem.soul.trim() + '\n\n' : '') +
1839
1857
  'Sen BEAST FİNANS\u2019sın — bilgisayardaki MetaTrader 5 (MT5) terminaline köprüyle bağlı trading ajanı.\n' +
1840
1858
  'ORTAM: Beast Finance uygulamasındasın — solda sohbet geçmişi + izleyiciler, ortada bu sohbet, SAĞ panelde MT5 işlem panosu (hesap, pozisyonlar, piyasa, AKIŞ) var.\n' +
1841
1859
  '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' +
1842
1860
  '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' +
1843
- modeBlock + '\n' +
1861
+ modeBlock2 + '\n' +
1844
1862
  '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' +
1845
1863
  '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' +
1864
+ '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").\n' +
1846
1865
  (symbols ? `İZLEME LİSTESİ: ${symbols}\n` : '') +
1847
1866
  (lim.maxLot ? `LİMİTLER: max lot ${lim.maxLot}` + (lim.maxPositions ? ` · max eşzamanlı pozisyon ${lim.maxPositions}` : '') + ' — bunları aşıp araç kullanma; sistem zaten reddeder.\n' : '') +
1848
1867
  'RİSK DİSİPLİNİ:\n' +
@@ -1851,6 +1870,8 @@ class Engine {
1851
1870
  '- Emin olmadığında İŞLEM YOK — "BEKLE: <sebep>" yaz. Sık işlem > iyi işlem.\n' +
1852
1871
  '- Hesap kaldıracı ve serbest marja göre pozisyon boyutunu düşük tut; tek işlemde serbest marjın büyük kısmını riske atma.\n' +
1853
1872
  (session && session.financeStrategy ? `SAHİBİNİN STRATEJİ NOTU (önceliklidir):\n${session.financeStrategy}\n` : '') +
1873
+ (mem.user ? `# USER\n${mem.user}\n` : '') +
1874
+ (rules.length ? `# KURALLAR (sahibinin kalıcı talimatları — daima uy)\n${rules.map((r) => '- ' + r).join('\n')}\n` : '') +
1854
1875
  (skList.length
1855
1876
  ? '# SKILLS\nKurulu skill kataloğu: görev bir skill\u2019in tanımına uyarsa skill aracıyla gövdesini (SKILL.md) yükleyip uygula — tekerleği yeniden icat etme. Analysis/rapor/PDF gibi işlerde skill\u2019in prosedürüne uy.\n' +
1856
1877
  skList.map((s) => `- ${s.name}: ${s.description} [${s.path}]`).join('\n') + '\n'
@@ -2797,6 +2818,15 @@ class Engine {
2797
2818
  if (adef && (adef.mode === 'bg' || adef.mode === 'all')) bg.agentName = adef.name;
2798
2819
  }
2799
2820
  this.cache.set(bg.id, bg);
2821
+ /* FİNANS İŞÇİSİ: parent finance oturumuyken (Beast Finance sohbeti) açılan
2822
+ arka plan işi mt5_* okuma araçlarını görür ve finance promptuyla koşar —
2823
+ chat Beast'i Finance'tan paralel finance ajanı açabilir */
2824
+ const parentSess = parent ? this.cache.get(parent) || null : null;
2825
+ if (parentSess && parentSess.finance) {
2826
+ bg.finance = true;
2827
+ bg.financeTrader = false; /* işçi — asla otonom trade, yalnız analiz/rapor */
2828
+ bg.financeAuto = false;
2829
+ }
2800
2830
  const ttl = String(title || t).replace(/\s+/g, ' ').trim().slice(0, 80) || 'arka plan görevi';
2801
2831
  /* #17 cache'teki nesneye de işaret koy — listSessions temiz başlık göstersin */
2802
2832
  bg.bgTitle = ttl;
@@ -3051,10 +3081,19 @@ class Engine {
3051
3081
  return { ok: true, job, messages: tail.slice(-30) };
3052
3082
  }
3053
3083
 
3054
- /* İş bitişi kaydı: status=done|error|aborted. Hata olursa üst sohbete haber gider. */
3084
+ /* İş bitişi kaydı: status=done|error|aborted. Hata olursa üst sohbete haber gider.
3085
+ SÜREKLİ (continuous) işler — Finance trade ajanları gibi tur tur koşanlar —
3086
+ 'done'/'error'da KAPANMAZ: tur sonu = aktivite tazelenir, iş koşmaya devam
3087
+ eder (döngüyü main yönetir). Yalnız 'aborted' (kullanıcı iptali) gerçek bitiştir. */
3055
3088
  _bgFinish(sid, status, errorMsg) {
3056
3089
  const job = this._bgJobs.get(String(sid));
3057
3090
  if (!job || job.status !== 'running') return;
3091
+ if (job.continuous && (status === 'done' || status === 'error')) {
3092
+ job.lastActivityAt = nowIso();
3093
+ if (status === 'error') job.errors = (job.errors || 0) + 1;
3094
+ this._bgEmit();
3095
+ return;
3096
+ }
3058
3097
  job.status = status;
3059
3098
  job.revive = false;
3060
3099
  job.endedAt = nowIso();
@@ -3122,9 +3161,11 @@ class Engine {
3122
3161
  job.lastActivityAt = nowIso();
3123
3162
  }
3124
3163
 
3125
- /* Saf sınıflandırma (test edilebilir): null | 'stuck' | 'long' */
3164
+ /* Saf sınıflandırma (test edilebilir): null | 'stuck' | 'long'
3165
+ continuous işler (Finance trade ajanları) denetlenmez — turlar arası
3166
+ boşluk normaldir; döngüyü main yönetir. */
3126
3167
  static superviseReason(job, nowMs) {
3127
- if (!job || job.status !== 'running') return null;
3168
+ if (!job || job.status !== 'running' || job.continuous) return null;
3128
3169
  const started = Date.parse(job.startedAt || '') || nowMs;
3129
3170
  const lastAct = Date.parse(job.lastActivityAt || '') || started;
3130
3171
  const runMin = (nowMs - started) / 60000;
@@ -3365,6 +3406,7 @@ class Engine {
3365
3406
  `- Bağımsız araç çağrılarını aynı turda PARALEL ver.\n` +
3366
3407
  `- PDF gerekirse pip\u2019ten paket kurma (Türkçe bozar); Node\u2019un kurulu \`pdf-lib\`+fontkit\u2019iyle .js script yazıp \`node\` ile çalıştır. md→pdf çevirme: belge çıktısını doğrudan PDF olarak üret.\n` +
3367
3408
  `- ARAŞTIRMA SINIRI: 3-5 kaynak yeter; süre hedefi ~3 dakika. 2-3 denemede bulunamayan bilgiyi BIRAK — bulabildiğin kısmi sonucu raporla ve neyi bulamadığını yaz. Kapalı/gizli içerik peşinde koşma.\n` +
3409
+ `AJAN KOORDİNASYONU: diğer koşan ajanlarla (paralel işler, finance ajanları) konuşman gerekiyorsa agent_dm aracını kullan — to: hedefin başlığındaki anahtar kelime (örn "GOLD", "Trader"), message: 1-3 cümlelik net mesaj. Senden istenen görevde başka bir ajanın zaten yaptığın işe ihtiyacı varsa DM ile haber ver.\n` +
3368
3410
  FORMAT_RULES + '\n' +
3369
3411
  `SON ÇIKTI: 3-5 satırlık net sonuç raporu, madde madde. Soru sorma, sohbet etme.`
3370
3412
  );
@@ -3421,6 +3463,11 @@ class Engine {
3421
3463
  );
3422
3464
  if (skillDef) toolsList = [...toolsList, skillDef];
3423
3465
  }
3466
+ /* AJAN DM: koşan ajan oturumlarına (arka plan işleri + finance
3467
+ ajanları) ajanlar-arası mesajlaşma aracı verilir */
3468
+ if (session && (session.bgJob || (session.finance && session.bgTitle))) {
3469
+ toolsList = [...toolsList, AGENT_DM_DEF];
3470
+ }
3424
3471
  /* panel_run yalnız SANDBOX oturumlarında görünsün — diğer panellerde
3425
3472
  modelin alet çantasında olmasın (hook olmadan çalışmaz) */
3426
3473
  if (!(session && session.sbSandbox)) {
@@ -3430,12 +3477,12 @@ class Engine {
3430
3477
  /* opencode agent.ts port: özel ajan tanımı — prompt/model/araç/steps */
3431
3478
  const adef = this._agentDefFor(session, !!session.bgJob);
3432
3479
  const promptText = this._lastUserText(session);
3433
- let system = session.bgJob
3434
- ? this.buildBgSystem(session)
3435
- : session.studio
3436
- ? this.buildStudioSystem(session)
3437
- : session.finance
3438
- ? this.buildFinanceSystem(session)
3480
+ let system = session.finance
3481
+ ? this.buildFinanceSystem(session) /* finance önceliklidir — bg finance işçileri de finance promptuyla koşar */
3482
+ : session.bgJob
3483
+ ? this.buildBgSystem(session)
3484
+ : session.studio
3485
+ ? this.buildStudioSystem(session)
3439
3486
  : bcAgent
3440
3487
  ? this.buildOcSystem(session, bcAgent)
3441
3488
  : await this.buildSystem(promptText, session);
@@ -4714,6 +4761,10 @@ const skills = require('./skills');
4714
4761
  if (name === 'skill') {
4715
4762
  return JSON.stringify(this._skillBody(args));
4716
4763
  }
4764
+ /* AJAN DM: ajanlar arası mesaj — hedef koşan ajanın oturumuna düşer */
4765
+ if (name === 'agent_dm') {
4766
+ return JSON.stringify(this._agentDmSend(sessionId, args));
4767
+ }
4717
4768
  if (isBc) {
4718
4769
  /* opencode permission akışı (tool/external-directory.ts + her aracın
4719
4770
  ctx.ask'ı): önce workspace DIŞI erişim izni, sonra araç izni.
@@ -5235,6 +5286,26 @@ function emitSafe(engine, sessionId, ev) {
5235
5286
  engine.emit({ ...ev, sessionId });
5236
5287
  }
5237
5288
 
5289
+ /* AJAN DM aracı: paralel/finance ajanlarının birbirine kısa mesaj atması.
5290
+ Salt metin — hedef ajanın oturumuna [AJAN DM] bloğu olarak düşer; AJAN DM
5291
+ panelinde iki yönlü görünür. */
5292
+ const AGENT_DM_DEF = {
5293
+ type: 'function',
5294
+ function: {
5295
+ name: 'agent_dm',
5296
+ description:
5297
+ 'Send a short DM to another RUNNING agent (parallel agents / finance agents) to coordinate: share findings, ask status, warn about risk, hand off work. `to` = target session id OR a keyword from the agent title (e.g. "GOLD", "Trader"). The message is delivered to the other agent and shown in the Agent DM panel.',
5298
+ parameters: {
5299
+ type: 'object',
5300
+ properties: {
5301
+ to: { type: 'string', description: 'Target agent session id or a title keyword' },
5302
+ message: { type: 'string', description: 'Short message (1-3 sentences)' },
5303
+ },
5304
+ required: ['to', 'message'],
5305
+ },
5306
+ },
5307
+ };
5308
+
5238
5309
  const TOOLS = [
5239
5310
  ...tools.definitions,
5240
5311
  {
@@ -6015,6 +6086,80 @@ Engine.prototype.clearTodos = function (sid) {
6015
6086
  return { ok: true, count: prev.length };
6016
6087
  };
6017
6088
 
6089
+ /* ---------- AJAN DM: ajanların birbirine mesajı ---------- */
6090
+
6091
+ /* Ajan DM gönder: hedef koşan paralel/finance ajanı (id YA DA başlık
6092
+ anahtar kelimesi). Teslim _pendingReports kuyruğuyla yapılır — hedef
6093
+ meşgulse mesaj done olunca düşer, kaybolmaz. Panel agent-dms.json'dan. */
6094
+ Engine.prototype._agentDmSend = function (fromSid, args) {
6095
+ try {
6096
+ const to = String((args && args.to) || '').trim();
6097
+ const text = String((args && args.message) || '').trim().slice(0, 1200);
6098
+ if (!text) return { ok: false, error: 'mesaj boş' };
6099
+ const jobs = this._bgJobs || new Map();
6100
+ let target = jobs.has(to) ? to : '';
6101
+ if (!target) {
6102
+ const q = to.toLowerCase();
6103
+ for (const [sid, j] of jobs) {
6104
+ if (sid === String(fromSid)) continue;
6105
+ if (j && j.title && String(j.title).toLowerCase().includes(q)) {
6106
+ target = sid;
6107
+ break;
6108
+ }
6109
+ }
6110
+ }
6111
+ if (!target || !jobs.has(target)) {
6112
+ const titles = [...jobs.values()]
6113
+ .filter((j) => j && j.status === 'running' && j.title)
6114
+ .map((j) => j.title)
6115
+ .slice(0, 8);
6116
+ return {
6117
+ ok: false,
6118
+ error: 'hedef ajan bulunamadı: "' + to + '"' + (titles.length ? ' — koşan ajanlar: ' + titles.join(', ') : ''),
6119
+ };
6120
+ }
6121
+ if (String(target) === String(fromSid)) return { ok: false, error: 'kendine DM atılamaz' };
6122
+ const fromJob = jobs.get(String(fromSid));
6123
+ const toJob = jobs.get(target);
6124
+ const dm = {
6125
+ at: new Date().toISOString(),
6126
+ from: String(fromSid),
6127
+ fromTitle: (fromJob && fromJob.title) || 'Ajan',
6128
+ to: String(target),
6129
+ toTitle: (toJob && toJob.title) || 'Ajan',
6130
+ text,
6131
+ };
6132
+ this._agentDms = this._agentDms || [];
6133
+ this._agentDms.push(dm);
6134
+ if (this._agentDms.length > 400) this._agentDms.splice(0, this._agentDms.length - 400);
6135
+ try {
6136
+ fs.writeFileSync(this._agentDmsFile, JSON.stringify({ dms: this._agentDms }, null, 2));
6137
+ } catch {}
6138
+ emitSafe(this, target, { type: 'agent-dm', ...dm });
6139
+ /* teslim: hedef meşgulse pending kuyruğunda bekler, done olunca düşer */
6140
+ (this._pendingReports = this._pendingReports || []).push({
6141
+ parentId: target,
6142
+ text:
6143
+ `[AJAN DM — ${dm.fromTitle}]\n${text}\n` +
6144
+ `(Cevabını agent_dm aracıyla ver — to: "${dm.fromTitle}")`,
6145
+ });
6146
+ this.flushPendingReports(target);
6147
+ return { ok: true, to: target, toTitle: dm.toTitle };
6148
+ } catch (e) {
6149
+ return { ok: false, error: String((e && e.message) || e) };
6150
+ }
6151
+ };
6152
+
6153
+ Engine.prototype.agentDmsList = function () {
6154
+ return [...(this._agentDms || [])].slice(-200);
6155
+ };
6156
+
6157
+ Engine.prototype.agentDmsClear = function () {
6158
+ this._agentDms = [];
6159
+ try { fs.rmSync(this._agentDmsFile, { force: true }); } catch {}
6160
+ return { ok: true };
6161
+ };
6162
+
6018
6163
  module.exports = Engine;
6019
6164
  module.exports.Engine = Engine;
6020
6165
  module.exports.sanitizeTodoItems = sanitizeTodoItems;
@@ -137,7 +137,7 @@ Get-Content "$env:APPDATA\\beast\\sessions\\bg-jobs.json" | ConvertFrom-Json | %
137
137
  | \`bin/beast-agent.js\` | CLI: \`beast\` (başlat), \`beast update\`, \`beast uninstall\` |
138
138
 
139
139
  ## SLASH KOMUTLARI
140
- /help · /version · /new · /open <kod> · /sessions · /stop · /start · /restart · /change [n] · /model <isim> · /think 0-5 · /clear · /notes · /rule <metin> · /rules · /notify on|off · /screenshot · /approve [always] · /deny · /update [now] · /usage · /backup · /status · /skills
140
+ /help · /version · /new · /open <kod> · /sessions · /stop · /start · /restart · /change [n] · /model <isim> · /think 0-5 · /clear · /autodel [all] (hatırlatmaları sil) · /deltodo [all] (todoları sil) · /cron list|clear|del <id> (WA) · /notes · /rule <metin> · /rules · /notify on|off · /screenshot · /approve [always] · /deny · /update [now] · /usage · /backup · /status · /skills
141
141
 
142
142
  ## AYAR SEKMELERİ
143
143
  Provider · Fallout · Skills · Paralel Ajanlar · TTS · E-posta · Entegrasyonlar · Web Arama · MCP · Olaylar · Cron · Maliyet · Loglar · Panel · Limitler · Güvenlik · Güncelleme
package/src/main.js CHANGED
@@ -5100,6 +5100,57 @@ ipcMain.handle('agent:send', (_e, { sessionId, text }) => {
5100
5100
  desktopEcho(sessionId, '/help', desktopSlashHelp());
5101
5101
  return true;
5102
5102
  }
5103
+ if (t === '/autodel' || t.startsWith('/autodel ')) {
5104
+ /* /autodel [all] — otomatik hatırlatmaları tek komutla sil (WA ile aynı davranış) */
5105
+ const arg = t.slice(8).trim().toLowerCase();
5106
+ if (arg === 'all' || arg === 'hepsi' || arg === 'hepsini') {
5107
+ const r = cron.clearAll();
5108
+ cronEmit();
5109
+ desktopEcho(sessionId, t, r.ok ? `**Tüm zamanlanmış görevler silindi** (${r.count} adet).` : 'Temizlenemedi.');
5110
+ } else {
5111
+ const r = cron.removeIf(isReminderJob);
5112
+ cronEmit();
5113
+ const left = cron.list().filter((j) => j.enabled).length;
5114
+ desktopEcho(
5115
+ sessionId,
5116
+ t,
5117
+ r.count
5118
+ ? `**${r.count} otomatik hatırlatma silindi.** Kalan aktif görev: ${left}`
5119
+ : 'Silinecek otomatik hatırlatma yok — zaten temiz.'
5120
+ );
5121
+ }
5122
+ return true;
5123
+ }
5124
+ if (t === '/deltodo' || t.startsWith('/deltodo ')) {
5125
+ /* /deltodo [all] — todo listelerini temizle (WA ile aynı davranış) */
5126
+ const arg = t.slice(8).trim().toLowerCase();
5127
+ if (arg === 'all' || arg === 'hepsi' || arg === 'hepsini') {
5128
+ let n = 0;
5129
+ let items = 0;
5130
+ try {
5131
+ for (const v of engine.listSessions()) {
5132
+ const r = engine.clearTodos(v.id);
5133
+ if (r && r.ok) {
5134
+ n++;
5135
+ items += r.count || 0;
5136
+ }
5137
+ }
5138
+ } catch {}
5139
+ desktopEcho(sessionId, t, n ? `**Tüm oturumların todoları temizlendi** (${n} oturum, ${items} madde).` : 'Temizlenecek todo yok.');
5140
+ } else {
5141
+ const r = engine.clearTodos(String(sessionId || ''));
5142
+ desktopEcho(
5143
+ sessionId,
5144
+ t,
5145
+ r.ok
5146
+ ? r.count
5147
+ ? `**${r.count} todo temizlendi.** (tüm oturumlar: /deltodo all)`
5148
+ : 'Todo listesi zaten boş. (tüm oturumlar: /deltodo all)'
5149
+ : 'Oturum bulunamadı — todo listesi yok.'
5150
+ );
5151
+ }
5152
+ return true;
5153
+ }
5103
5154
  if (t === '/rules') {
5104
5155
  const rs = memory.listRules();
5105
5156
  desktopEcho(sessionId, t, rs.length ? '**Kalıcı kurallar:**\n' + rs.map((r0, i) => `${i + 1}. ${r0}`).join('\n') : 'Kalıcı kural yok — ekle: **/rule <metin>**');
@@ -5294,6 +5345,8 @@ function desktopSlashHelp() {
5294
5345
  '**/think 0-5** – düşünme seviyesi (0 kapalı · 5 max)',
5295
5346
  '**/agent [isim]** – özel ajan bağla / listele (%APPDATA%\\beast\\agents\\*.md)',
5296
5347
  '**/clear** – oturum geçmişini gerçekten sil (kod korunur)',
5348
+ '**/autodel** – tüm otomatik hatırlatmaları tek komutla sil · **/autodel all** – cron görevleri dahil hepsi',
5349
+ '**/deltodo** – bu oturumun todo listesini temizle · **/deltodo all** – tüm oturumların todoları',
5297
5350
  '**/notes** – bu oturumun notlarını göster',
5298
5351
  '**/rule <metin>** – kalıcı kural ekle · **/rules** – listele',
5299
5352
  '**/notify on|off** – hata mail bildirimini aç/kapa',
@@ -5827,9 +5880,18 @@ ipcMain.handle('memory:get', () => memory.loadAll());
5827
5880
  /* paralel ajanlar: canlı izleme (#14) */
5828
5881
  ipcMain.handle('agents:list', () => engine.listBgJobs());
5829
5882
  ipcMain.handle('agents:detail', (_e, id) => engine.bgDetail(id));
5830
- ipcMain.handle('agents:cancel', (_e, id) => ({
5831
- ok: engine.interrupt(String(id || ''), 'kullanıcı Paralel Ajanlar panelinden (×) iptal etti'),
5832
- }));
5883
+ ipcMain.handle('agents:cancel', (_e, id) => {
5884
+ const sid = String(id || '');
5885
+ /* Finance ajanı (sürekli iş): interrupt yetmez — döngüyü de kapat */
5886
+ if (financeState && financeState.agents && financeState.agents.has(sid)) {
5887
+ finAgentStop(sid, 'kullanıcı Paralel Ajanlar panelinden (×) iptal etti');
5888
+ return { ok: true };
5889
+ }
5890
+ return { ok: !!engine.interrupt(sid, 'kullanıcı Paralel Ajanlar panelinden (×) iptal etti') };
5891
+ });
5892
+ /* AJAN DM paneli: ajanlar arası mesaj arşivi */
5893
+ ipcMain.handle('agent-dms:list', () => (engine ? engine.agentDmsList() : []));
5894
+ ipcMain.handle('agent-dms:clear', () => (engine ? engine.agentDmsClear() : { ok: false }));
5833
5895
  ipcMain.handle('ceo:get', () => !!engine.ceoMode);
5834
5896
  ipcMain.handle('ceo:set', (_e, v) => {
5835
5897
  settings.ceoMode = !!v;
@@ -8214,13 +8276,13 @@ const financetools = require('./agent/financetools');
8214
8276
 
8215
8277
  const financeState = {
8216
8278
  mode: false, /* chat oturumları finance bayrağı alıyor mu */
8217
- traderSid: null,
8279
+ traderSid: null, /* ANA trader (panel kartı buna bağlı) */
8218
8280
  traderOn: false,
8219
- traderTimer: null,
8220
8281
  traderRounds: 0,
8221
8282
  lastRoundAt: 0,
8222
8283
  installing: false,
8223
8284
  installTried: false,
8285
+ agents: new Map(), /* sid -> { symbols, main, round, timer } — AYNI ANDA koşan finance ajanları */
8224
8286
  };
8225
8287
 
8226
8288
  function financeDir() {
@@ -8232,7 +8294,10 @@ function financeDir() {
8232
8294
  function finCfg() {
8233
8295
  if (!settings.finance || typeof settings.finance !== 'object') settings.finance = {};
8234
8296
  const f = settings.finance;
8235
- if (!Array.isArray(f.symbols) || !f.symbols.length) f.symbols = ['EURUSD', 'XAUUSD', 'GBPUSD', 'BTCUSD'];
8297
+ /* SADECE kullanıcının eklediği semboller görünür — artık varsayılan sembol YOK
8298
+ (eski davranış EURUSD/XAUUSD/GBPUSD/BTCUSD'yi geri geri getiriyordu) */
8299
+ if (!Array.isArray(f.symbols)) f.symbols = [];
8300
+ if (typeof f.consultChat !== 'boolean') f.consultChat = true; /* her tur öncesi chat ajanından plan */
8236
8301
  if (!Number(f.intervalSec)) f.intervalSec = 120;
8237
8302
  if (!Number(f.maxLot)) f.maxLot = 0.1;
8238
8303
  if (f.maxPositions == null) f.maxPositions = 3;
@@ -8334,97 +8399,256 @@ mt5bridge.on('log', (m) => {
8334
8399
  financeLog('[mt5] ' + line.slice(0, 300));
8335
8400
  });
8336
8401
 
8337
- /* ---------- TRADER ajanı (gizli engine oturumu + tur döngüsü) ---------- */
8402
+ /* ---------- FINANCE AJANLARI (paralel ajan mimarisi) ----------
8403
+ Her finance ajanı = SÜREKLİ (continuous) paralel ajan: Paralel Ajanlar
8404
+ panelinde canlı görünür, hareketleri (tool-start/status) panelde akar,
8405
+ birden fazla ajan AYNI ANDA koşabilir (ana trader + sembol başına ayrı
8406
+ ajanlar). Döngüyü main yönetir; engine 'done'/'error'da sürekli işi
8407
+ KAPATMAZ (_bgFinish continuous dalı), yalnız kullanıcı iptali bitirir. */
8338
8408
 
8339
- function finTraderSession() {
8340
- let sid = financeState.traderSid;
8341
- if (sid) {
8342
- try {
8343
- const s = engine.cache.get(sid);
8344
- if (s) return s;
8345
- } catch {}
8346
- financeState.traderSid = null;
8409
+ function finAgentSession(symbols, isMain) {
8410
+ if (isMain) {
8411
+ const sid = financeState.traderSid;
8412
+ if (sid) {
8413
+ try {
8414
+ const s = engine.cache.get(sid);
8415
+ if (s) return s;
8416
+ } catch {}
8417
+ financeState.traderSid = null;
8418
+ }
8347
8419
  }
8348
8420
  const s = engine._load(engine.createSession().id);
8349
8421
  s.messages = s.messages || [];
8350
- s.bgTitle = 'Beast Finance · Trader'; /* _view.isBg → sohbet geçmişinde gizli */
8422
+ const symLabel = symbols && symbols.length ? symbols.join(', ').slice(0, 40) : 'tüm liste';
8423
+ const title = isMain ? 'Beast Finance · Trader' : 'Finance · ' + symLabel;
8424
+ s.bgTitle = title; /* _view.isBg → sohbet geçmişinde gizli */
8351
8425
  try {
8352
8426
  fs.appendFileSync(
8353
8427
  engine._file(s.id),
8354
- JSON.stringify({ t: 'meta2', bgOf: '', title: 'Beast Finance · Trader', at: new Date().toISOString() }) + '\n'
8428
+ JSON.stringify({ t: 'meta2', bgOf: '', title, at: new Date().toISOString() }) + '\n'
8355
8429
  );
8356
8430
  } catch {}
8357
8431
  engine.cache.set(s.id, s);
8358
- engine.markFinance(s.id, true); /* kalıcı finance etiketi (trader rolüyle) */
8359
- s.workspace = financeDir(); /* trader kendi klasöründe çalışır */
8360
- financeState.traderSid = s.id;
8432
+ engine.markFinance(s.id, true); /* kalıcı finance etiketi (mt5_* araçları) */
8433
+ s.workspace = financeDir(); /* ajan kendi klasöründe çalışır */
8434
+ if (isMain) financeState.traderSid = s.id;
8361
8435
  return s;
8362
8436
  }
8363
8437
 
8364
- function finApplyTraderFields(s) {
8438
+ /* Ajanı Paralel Ajanlar paneline kaydet — sürekli (continuous) iş olarak.
8439
+ Panelde canlı hareket akışı + × ile durdurma buradan gelir. */
8440
+ function finAgentRegisterBg(s, symbols) {
8441
+ try {
8442
+ if (!engine || !engine._bgJobs) return;
8443
+ const f = finCfg();
8444
+ const symLabel = symbols && symbols.length ? symbols.join(', ').slice(0, 40) : 'tüm izleme listesi';
8445
+ engine._bgJobs.set(String(s.id), {
8446
+ id: s.id,
8447
+ code: s.code,
8448
+ title: s.bgTitle || 'Finance Ajanı',
8449
+ task: `Beast Finance ajanı (${symLabel}) — ${f.allowTrading ? 'otonom tur tur işlem yönetimi' : 'analiz modunda tur tur tarama'}`,
8450
+ agent: null,
8451
+ parentId: '',
8452
+ groupId: null,
8453
+ status: 'running',
8454
+ slot: false, /* eşzamanlılık slotu TÜKETMEZ — ana işleri bloklamaz */
8455
+ continuous: true, /* tur sonları işi KAPATMAZ (_bgFinish) */
8456
+ startedAt: new Date().toISOString(),
8457
+ lastActivityAt: new Date().toISOString(),
8458
+ lastNudgeAt: null,
8459
+ checks: 0,
8460
+ fixes: 0,
8461
+ endedAt: null,
8462
+ error: null,
8463
+ });
8464
+ engine._bgEmit();
8465
+ } catch {}
8466
+ }
8467
+
8468
+ function finApplyTraderFields(s, symbolsOverride) {
8365
8469
  const f = finCfg();
8366
8470
  s.finance = true;
8367
8471
  s.financeTrader = true;
8368
8472
  s.financeAuto = f.allowTrading === true;
8369
- s.financeSymbols = f.symbols;
8473
+ s.financeSymbols = Array.isArray(symbolsOverride) && symbolsOverride.length ? symbolsOverride : f.symbols;
8370
8474
  s.financeStrategy = String(f.strategy || '');
8371
8475
  s.financeLimits = { maxLot: f.maxLot, maxPositions: f.maxPositions };
8372
8476
  engine.cache.set(String(s.id), s);
8373
8477
  }
8374
8478
 
8375
- function finTraderBrief() {
8479
+ /* Yeni finance ajanı aç (ana trader YA DA sembol işçisi) */
8480
+ function finAgentCreate(symbols, isMain) {
8481
+ const f = finCfg();
8482
+ const s = finAgentSession(symbols, isMain);
8483
+ const record = {
8484
+ symbols: symbols && symbols.length ? symbols.slice(0, 10) : [...(f.symbols || [])],
8485
+ main: !!isMain,
8486
+ round: 0,
8487
+ timer: null,
8488
+ };
8489
+ financeState.agents.set(String(s.id), record);
8490
+ finApplyTraderFields(s, record.symbols);
8491
+ finAgentRegisterBg(s, record.symbols);
8492
+ return { s, agent: record };
8493
+ }
8494
+
8495
+ /* Ajanı durdur: timer kapat + oturumu kes + paralel ajan kaydını 'aborted' yaz */
8496
+ function finAgentStop(sid, reason) {
8497
+ sid = String(sid || '');
8498
+ const agent = financeState.agents.get(sid);
8499
+ if (!agent) return false;
8500
+ clearTimeout(agent.timer);
8501
+ financeState.agents.delete(sid);
8502
+ if (sid === String(financeState.traderSid || '')) financeState.traderSid = null;
8503
+ try {
8504
+ if (engine && engine.isBusy(sid)) {
8505
+ engine.interrupt(sid, reason || 'kullanıcı finance ajanını durdurdu');
8506
+ }
8507
+ } catch {}
8508
+ try {
8509
+ const j = engine && engine._bgJobs ? engine._bgJobs.get(sid) : null;
8510
+ if (j && j.status === 'running') {
8511
+ j.status = 'aborted';
8512
+ j.endedAt = new Date().toISOString();
8513
+ j.error = String(reason || 'kullanıcı durdurdu').slice(0, 200);
8514
+ engine._bgEmit();
8515
+ }
8516
+ } catch {}
8517
+ finPush('trader', { state: 'idle', rounds: financeState.traderRounds });
8518
+ return true;
8519
+ }
8520
+
8521
+ function finTraderBrief(agent) {
8376
8522
  const f = finCfg();
8523
+ const syms = agent && agent.symbols && agent.symbols.length ? agent.symbols.join(', ') : (f.symbols || []).join(', ');
8524
+ const who = agent && agent.main ? 'TRADER' : 'FINANCE AJANI';
8377
8525
  return [
8378
- 'Beast Finance TRADER başlatıldı — ilk tur: strateji çerçeveni kur ve piyasa taramasını yap.',
8379
- `İzleme listesi: ${(f.symbols || []).join(', ')}`,
8526
+ `Beast Finance ${who} başlatıldı — ilk tur: strateji çerçeveni kur ve piyasa taramasını yap.`,
8527
+ `Odak semboller: ${syms || '(boş — mt5_status ile terminale bak, mantıklı semboller seç)'}`,
8380
8528
  `Tur aralığı: ${f.intervalSec} sn · Max lot: ${f.maxLot} · Max eşzamanlı pozisyon: ${f.maxPositions}`,
8381
8529
  f.allowTrading
8382
8530
  ? 'Otomatik işlem AÇIK: mt5_trade/mt5_close/mt5_modify/mt5_pending kullanabilirsin (limitler sistemce zorlanır).'
8383
8531
  : 'Otomatik işlem KAPALI: SADECE analiz + net işlem önerileri yaz (sembol, yön, giriş, SL, TP, sebep); işlem açma.',
8384
8532
  f.strategy ? `Sahibinin strateji notu: ${f.strategy}` : 'Strateji notu yok: trend + destek/direnç + momentum ile temel okuma yap.',
8385
8533
  'Bu turda: mt5_status → hesap/pozisyon/fiyat verisi → değerlendirme → kararlar (veya BEKLE: sebep) → kısa rapor.',
8534
+ 'Diğer finance/paralel ajanlarla koordinasyon için agent_dm aracı var (to: ajan başlığı anahtar kelimesi).',
8386
8535
  ].join('\n');
8387
8536
  }
8388
8537
 
8389
- function finTraderRound() {
8390
- if (!financeState.traderOn) return;
8391
- if (!engine) return;
8392
- const s = finTraderSession();
8393
- finApplyTraderFields(s);
8394
- if (engine.isBusy(s.id)) {
8538
+ /* Her tur öncesi CHAT AJANINDAN plan iste: aynı motorun odaklı alt-ajanı
8539
+ (_subagent) hesap/pozisyon/strateji bağlamıyla kısa işlem planı üretir.
8540
+ Plan, tur mesajına [ANA AJAN PLANI] bloğu olarak eklenir. */
8541
+ async function finConsultPlan(f, agent, sid) {
8542
+ if (!engine) return '';
8543
+ let account = null;
8544
+ let positions = [];
8545
+ try {
8546
+ if (mt5bridge.running) {
8547
+ const [ac, ps] = await Promise.all([
8548
+ mt5bridge.call('account', {}, 8000).catch(() => null),
8549
+ mt5bridge.call('positions', {}, 8000).catch(() => null),
8550
+ ]);
8551
+ if (ac && ac.ok) account = ac.data && ac.data.account;
8552
+ if (ps && ps.ok) positions = (ps.data && ps.data.positions) || [];
8553
+ }
8554
+ } catch {}
8555
+ const auto = f.allowTrading ? 'AÇIK (işlem açabilir)' : 'KAPALI (yalnız öneri)';
8556
+ const task =
8557
+ 'Beast Finance ajanı yeni tura giriyor. SEN mt5_* araçlarını KULLANMA, işlem AÇMA — ' +
8558
+ 'sana düşen iş: semboller için bu turun kısa işlem PLANINI üretmek ' +
8559
+ '(sembol başına tek satır: AL/SAT/BEKLE + sebep + giriş/SL/TP fikri). ' +
8560
+ 'Güncel piyasa bağlamı gerekiyorsa web_search kullan; kurulu bir skill konuyla ilgiliyse skill aracıyla oku.';
8561
+ const ctx = [
8562
+ `Odak semboller: ${agent && agent.symbols && agent.symbols.length ? agent.symbols.join(', ') : (f.symbols || []).join(', ') || '(boş — ajan kendi bulabilir)'}`,
8563
+ `Otomatik işlem: ${auto} · max lot ${f.maxLot} · max eşzamanlı pozisyon ${f.maxPositions}`,
8564
+ account
8565
+ ? `Hesap: bakiye ${account.balance} ${account.currency} · özkaynak ${account.equity ?? '?'} · serbest marj ${account.margin_free ?? '?'} · kaldıraç 1:${account.leverage ?? '?'}`
8566
+ : 'Hesap verisi alınamadı.',
8567
+ positions.length
8568
+ ? 'Açık pozisyonlar:\n' +
8569
+ positions
8570
+ .slice(0, 10)
8571
+ .map((p) => {
8572
+ const t = String(p.type ?? '');
8573
+ const side = /sell|1/.test(t) ? 'SATIŞ' : 'ALIŞ';
8574
+ return `- ${p.symbol || '?'} ${side} ${p.volume || '?'} lot @ ${p.price_open || '?'} · kâr/zarar ${p.profit ?? '?'}`;
8575
+ })
8576
+ .join('\n')
8577
+ : 'Açık pozisyon yok.',
8578
+ f.strategy ? `Sahibinin strateji notu: ${f.strategy}` : 'Strateji notu yok — temel teknik okuma (trend + destek/direnç + momentum).',
8579
+ 'Plan EN FAZLA ~15 satır; planı işlem yapmadan sadece METİN olarak döndür.',
8580
+ ].join('\n');
8581
+ try {
8582
+ const signal = typeof AbortSignal !== 'undefined' && AbortSignal.timeout ? AbortSignal.timeout(90000) : undefined;
8583
+ const res = await engine._subagent(task, ctx, signal, sid || null, '');
8584
+ return String(res || '').trim().slice(0, 4000);
8585
+ } catch {
8586
+ return '';
8587
+ }
8588
+ }
8589
+
8590
+ /* Bir ajanın TEK turu: (opsiyonel) ana ajan planı + tur emri */
8591
+ function finAgentRound(sid) {
8592
+ const agent = financeState.agents.get(String(sid));
8593
+ if (!agent || !engine) return;
8594
+ if (engine.isBusy(sid)) {
8395
8595
  /* hâlâ çalışıyor — done eventinde tekrar planlanır */
8396
8596
  return;
8397
8597
  }
8398
- financeState.traderRounds += 1;
8598
+ agent.round += 1;
8599
+ if (agent.main) financeState.traderRounds = agent.round;
8399
8600
  const f = finCfg();
8601
+ try {
8602
+ const s = engine.cache.get(String(sid));
8603
+ if (s) finApplyTraderFields(s, agent.symbols);
8604
+ } catch {}
8400
8605
  const auto = f.allowTrading
8401
8606
  ? 'İşlem açabilirsin — limitlere uy, SL\u2019siz pozisyon bırakma.'
8402
8607
  : 'Otomatik işlem KAPALI — sadece analiz + öneri.';
8403
- const ok = engine.send(
8404
- s.id,
8405
- `FINANCE TUR #${financeState.traderRounds}: hesap + pozisyonlar + izleme listesi fiyatlarını ç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.`,
8406
- { userAction: false }
8407
- );
8408
- if (ok) {
8409
- financeState.lastRoundAt = Date.now();
8410
- finPush('trader', { state: 'running', round: financeState.traderRounds });
8411
- } else {
8412
- /* stop kapısı/yoğunluk — yarım dakika sonra sessizce tekrar dene */
8413
- clearTimeout(financeState.traderTimer);
8414
- financeState.traderTimer = setTimeout(() => { try { finTraderRound(); } catch {} }, 30000);
8608
+ const focus = agent.symbols.length ? `Odak: ${agent.symbols.join(', ')}. ` : '';
8609
+ 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.`;
8610
+ const launch = (planBlock) => {
8611
+ if (!financeState.agents.has(String(sid))) return;
8612
+ const ok = engine.send(sid, planBlock + round, { userAction: false });
8613
+ if (ok) {
8614
+ financeState.lastRoundAt = Date.now();
8615
+ if (agent.main) finPush('trader', { state: 'running', round: agent.round });
8616
+ } else {
8617
+ /* stop kapısı/yoğunluk — yarım dakika sonra sessizce tekrar dene */
8618
+ clearTimeout(agent.timer);
8619
+ agent.timer = setTimeout(() => { try { finAgentRound(sid); } catch {} }, 30000);
8620
+ }
8621
+ };
8622
+ if (f.consultChat === false) {
8623
+ /* danışma kapalı: tur doğrudan başlar */
8624
+ launch('');
8625
+ return;
8415
8626
  }
8627
+ if (agent.main) finPush('trader', { state: 'consult', round: agent.round });
8628
+ finConsultPlan(f, agent, String(sid))
8629
+ .then((plan) => launch(plan ? `[ANA AJAN PLANI — bu turun varsayılan stratejisi; strateji notuyla çelişirse not önceliklidir]\n${plan}\n\n` : ''))
8630
+ .catch(() => launch(''));
8416
8631
  }
8417
8632
 
8633
+ /* Tur/durum sonları: sürekli ajan döngüsünü besle; kullanıcı iptalinde kapat */
8418
8634
  function finFlushOnDone(ev) {
8419
8635
  if (!ev || (ev.type !== 'done' && ev.type !== 'error')) return;
8420
- if (String(ev.sessionId || '') !== String(financeState.traderSid || '')) return;
8421
- if (!financeState.traderOn) return;
8422
- clearTimeout(financeState.traderTimer);
8636
+ const sid = String(ev.sessionId || '');
8637
+ const agent = financeState.agents.get(sid);
8638
+ if (!agent) return;
8639
+ /* Paralel Ajanlar panelinden × (interrupt abort'u) → ajanı gerçekten kapat */
8640
+ if (ev.type === 'done' && ev.aborted) {
8641
+ finAgentStop(sid, String(ev.reason || 'kullanıcı durdurdu'));
8642
+ return;
8643
+ }
8644
+ clearTimeout(agent.timer);
8423
8645
  const f = finCfg();
8424
8646
  const iv = Math.max(30, Number(f.intervalSec) || 120) * 1000;
8425
8647
  financeState.lastRoundAt = Date.now();
8426
- finPush('trader', { state: 'idle', round: financeState.traderRounds, nextInSec: iv / 1000 });
8427
- financeState.traderTimer = setTimeout(() => { try { finTraderRound(); } catch {} }, iv);
8648
+ if (agent.main) {
8649
+ finPush('trader', { state: 'idle', round: agent.round, nextInSec: iv / 1000 });
8650
+ }
8651
+ agent.timer = setTimeout(() => { try { finAgentRound(sid); } catch {} }, iv);
8428
8652
  }
8429
8653
 
8430
8654
  ipcMain.handle('finance:state', async () => {
@@ -8531,36 +8755,76 @@ ipcMain.handle('finance:trader:start', async () => {
8531
8755
  if (!engine) return { ok: false, error: 'ajan hazır değil' };
8532
8756
  const f = finCfg();
8533
8757
  if (!engine.publicState().hasModel && !f.traderSel) return { ok: false, error: 'model yok — Ayarlar → Provider' };
8534
- const s = finTraderSession();
8535
- finApplyTraderFields(s);
8536
- try { engine.setSessionModel(s.id, f.traderSel || null); } catch {}
8758
+ /* ANA trader: varsa aynen sürdür, yoksa yeni sürekli ajan aç */
8759
+ let mainSid = '';
8760
+ let mainAgent = null;
8761
+ for (const [sid, a] of financeState.agents) {
8762
+ if (a.main) {
8763
+ mainSid = sid;
8764
+ mainAgent = a;
8765
+ break;
8766
+ }
8767
+ }
8768
+ if (!mainSid) {
8769
+ const created = finAgentCreate([], true);
8770
+ mainSid = String(created.s.id);
8771
+ mainAgent = created.agent;
8772
+ }
8773
+ try { engine.setSessionModel(mainSid, f.traderSel || null); } catch {}
8537
8774
  financeState.traderOn = true;
8538
- financeState.traderRounds = 0;
8539
8775
  try { engine.clearStop(); } catch {}
8540
- const ok = engine.send(s.id, finTraderBrief(), { userAction: true });
8776
+ const ok = engine.send(mainSid, finTraderBrief(mainAgent), { userAction: true });
8541
8777
  if (!ok) {
8542
8778
  financeState.traderOn = false;
8543
8779
  return { ok: false, error: 'trader oturumu meşgul — birkaç saniye sonra tekrar dene' };
8544
8780
  }
8545
8781
  financeState.lastRoundAt = Date.now();
8546
8782
  financeLog('[trader] başlatıldı (model: ' + (f.traderSel || 'genel aktif model') + ')');
8547
- finPush('trader', { state: 'running', round: 0 });
8548
- return { ok: true, sid: s.id };
8783
+ finPush('trader', { state: 'running', round: mainAgent.round });
8784
+ return { ok: true, sid: mainSid };
8549
8785
  });
8550
8786
 
8551
8787
  ipcMain.handle('finance:trader:stop', async () => {
8552
8788
  financeState.traderOn = false;
8553
- clearTimeout(financeState.traderTimer);
8554
- financeState.traderTimer = null;
8555
- let interrupted = false;
8556
- if (financeState.traderSid && engine && engine.isBusy(financeState.traderSid)) {
8557
- try { interrupted = engine.interrupt(financeState.traderSid, 'kullanıcı Beast Finance trader ajanını durdurdu'); } catch {}
8789
+ /* ANA trader'ı durdur (sembol işçileri rail × ile ayrı durdurulur) */
8790
+ let mainSid = '';
8791
+ for (const [sid, a] of financeState.agents) {
8792
+ if (a.main) {
8793
+ mainSid = sid;
8794
+ break;
8795
+ }
8558
8796
  }
8797
+ const stopped = mainSid ? finAgentStop(mainSid, 'kullanıcı Beast Finance trader ajanını durdurdu') : false;
8798
+ const interrupted = !!stopped;
8559
8799
  financeLog('[trader] durduruldu');
8560
8800
  finPush('trader', { state: 'stopped' });
8561
8801
  return { ok: true, interrupted };
8562
8802
  });
8563
8803
 
8804
+ /* SEMBOL İŞÇİSİ: PİYASA kartındaki ▶ ile sembol başına AYRI finance ajanı —
8805
+ hepsi AYNI ANDA koşar, Paralel Ajanlar panelinde canlı görünür */
8806
+ ipcMain.handle('finance:agent:spawn', async (_e, payload) => {
8807
+ if (!engine) return { ok: false, error: 'ajan hazır değil' };
8808
+ const f = finCfg();
8809
+ if (!engine.publicState().hasModel && !f.traderSel) return { ok: false, error: 'model yok — Ayarlar → Provider' };
8810
+ const symbol = String((payload && payload.symbol) || '').trim().toUpperCase();
8811
+ if (!symbol) return { ok: false, error: 'sembol gerekli' };
8812
+ for (const [, a] of financeState.agents) {
8813
+ if (!a.main && a.symbols.length === 1 && a.symbols[0] === symbol) {
8814
+ return { ok: false, error: symbol + ' için ajan zaten koşuyor (Paralel Ajanlar panelinden durdurabilirsin)' };
8815
+ }
8816
+ }
8817
+ const created = finAgentCreate([symbol], false);
8818
+ try { engine.setSessionModel(created.s.id, f.traderSel || null); } catch {}
8819
+ const ok = engine.send(created.s.id, finTraderBrief(created.agent), { userAction: true });
8820
+ if (!ok) {
8821
+ finAgentStop(String(created.s.id), 'oturum meşgul — ajan başlatılamadı');
8822
+ return { ok: false, error: 'ajan oturumu meşgul — birkaç saniye sonra tekrar dene' };
8823
+ }
8824
+ financeLog('[ajan] ' + symbol + ' işçisi başlatıldı');
8825
+ return { ok: true, sid: created.s.id, title: created.s.bgTitle };
8826
+ });
8827
+
8564
8828
  ipcMain.handle('finance:connect', async () => {
8565
8829
  mt5bridge.stop();
8566
8830
  const st = financeEnsureBridge();
package/src/preload.js CHANGED
@@ -20,6 +20,8 @@ contextBridge.exposeInMainWorld('beast', {
20
20
  agentsList: () => ipcRenderer.invoke('agents:list'),
21
21
  agentsDetail: (id) => ipcRenderer.invoke('agents:detail', id),
22
22
  agentsCancel: (id) => ipcRenderer.invoke('agents:cancel', id),
23
+ agentDmsList: () => ipcRenderer.invoke('agent-dms:list'),
24
+ agentDmsClear: () => ipcRenderer.invoke('agent-dms:clear'),
23
25
  getCeoMode: () => ipcRenderer.invoke('ceo:get'),
24
26
  setCeoMode: (v) => ipcRenderer.invoke('ceo:set', v),
25
27
  listNotes: () => ipcRenderer.invoke('notes:list'),
@@ -106,6 +108,7 @@ contextBridge.exposeInMainWorld('beast', {
106
108
  financeSettings: (patch) => ipcRenderer.invoke('finance:settings', patch),
107
109
  financeTraderStart: () => ipcRenderer.invoke('finance:trader:start'),
108
110
  financeTraderStop: () => ipcRenderer.invoke('finance:trader:stop'),
111
+ financeAgentSpawn: (symbol) => ipcRenderer.invoke('finance:agent:spawn', { symbol }),
109
112
  financeConnect: () => ipcRenderer.invoke('finance:connect'),
110
113
  financeClose: (ticket, volume) => ipcRenderer.invoke('finance:close', { ticket, volume }),
111
114
  financeInstall: () => ipcRenderer.invoke('finance:install'),
@@ -18,6 +18,7 @@
18
18
  tipThink: 'Düşünme (reasoning) seviyesi',
19
19
  tipPickCfg: 'Picker\u2019da görünecek modeller',
20
20
  tipRail: 'Paralel Ajan Konsolu',
21
+ tipAgentDm: 'Ajan DM — ajanlar arası konuşma',
21
22
  tipNet: 'İnternet durumu',
22
23
  tip_net_online: 'İnternet: bağlı',
23
24
  tip_net_offline: 'İnternet: yok — bağlantı dönünce işler otomatik sürer',
@@ -61,6 +62,7 @@
61
62
  tipLang: 'Dil değiştir / Switch language',
62
63
  // kenar çubuğu & paneller
63
64
  labelParallel: 'Paralel Ajanlar',
65
+ labelAgentDm: 'Ajan DM',
64
66
  set_title: 'AYARLAR',
65
67
  tab_lang: 'Dil',
66
68
  lang_sub: 'Arayüz dili — uygulama dili ve sesli komut dili buradan değişir',
@@ -663,6 +665,7 @@
663
665
  tipThink: 'Reasoning level',
664
666
  tipPickCfg: 'Models shown in picker',
665
667
  tipRail: 'Parallel Agents Console',
668
+ tipAgentDm: 'Agent DM — agent-to-agent chat',
666
669
  tipNet: 'Internet status',
667
670
  tip_net_online: 'Internet: connected',
668
671
  tip_net_offline: 'Internet: offline — work resumes automatically once the connection returns',
@@ -704,6 +707,7 @@
704
707
  tipThemeLight: 'Light theme',
705
708
  tipLang: 'Dil değiştir / Switch language',
706
709
  labelParallel: 'Parallel Agents',
710
+ labelAgentDm: 'Agent DM',
707
711
  set_title: 'SETTINGS',
708
712
  tab_lang: 'Language',
709
713
  lang_sub: 'Interface language — app UI and voice command language',
@@ -94,6 +94,7 @@
94
94
  <button id="finBtn" class="dd-btn dd-icon-btn" title="Beast Finance — MT5 trade paneli (bilgisayardaki MetaTrader 5 ile çalışır)"><svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 3v18h18"/><path d="M7 15l4-5 3 3 5-7"/></svg></button>
95
95
  <button id="sbBtn" class="dd-btn dd-icon-btn" title="Beast Sandbox — GitHub reposu indir &amp; içinde çalış" data-i18n-title="tipSandbox"><svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/></svg></button>
96
96
  <button id="railBtn" class="dd-btn dd-icon-btn" title="Paralel Ajan Konsolu" data-i18n-title="tipRail">&#x25A4;</button>
97
+ <button id="dmBtn" class="dd-btn dd-icon-btn" title="Ajan DM — ajanlar arası konuşma" data-i18n-title="tipAgentDm">&#x2709;&#xFE0E;</button>
97
98
  <button id="watchBtn" class="dd-btn dd-icon-btn" title="İzleyiciler (Watchers)" data-i18n-title="tipWatch">&#x25CE;</button>
98
99
  <button id="cronBtn" class="dd-btn dd-icon-btn" title="Cron Görevler" data-i18n-title="tipCron">&#x23F1;&#xFE0E;</button>
99
100
  <button id="termCBtn" class="dd-btn dd-icon-btn" title="CMD terminali" data-i18n-title="tipTermC">&#x276F;&#xFE0E;</button>
@@ -346,6 +347,19 @@
346
347
  <span class="foot-label2" data-i18n="labelParallel">Paralel Ajanlar</span>
347
348
  </div>
348
349
  </aside>
350
+
351
+ <aside id="dmRail" class="dm-hidden">
352
+ <div id="dmRailHead">
353
+ <span id="dmRailTitle" data-i18n="labelAgentDm">AJAN DM</span>
354
+ <button id="dmClear" title="DM geçmişini temizle" aria-label="DM geçmişini temizle">
355
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#111" stroke-width="2.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>
356
+ </button>
357
+ </div>
358
+ <div id="dmRailList"></div>
359
+ <div id="sideFoot2">
360
+ <span class="foot-label2" data-i18n="labelAgentDm">Ajan DM</span>
361
+ </div>
362
+ </aside>
349
363
  </div>
350
364
 
351
365
  <div id="settingsOverlay" hidden>
@@ -42,6 +42,9 @@ const els = {
42
42
  netDot: $('#netDot'),
43
43
  railBtn: $('#railBtn'),
44
44
  railClear: $('#railClear'),
45
+ dmBtn: $('#dmBtn'),
46
+ dmRailList: $('#dmRailList'),
47
+ dmClear: $('#dmClear'),
45
48
  watchBtn: $('#watchBtn'),
46
49
  watchPaneList: $('#watchPaneList'),
47
50
  watchPaneOpen: $('#watchPaneOpen'),
@@ -4999,6 +5002,113 @@ async function loadRailChat(id) {
4999
5002
  }
5000
5003
  }
5001
5004
 
5005
+ /* ---------------- AJAN DM sütunu (ajanlar arası konuşma) ----------------
5006
+ rail ile aynı mimari: sağda ikinci sütun, dmBtn ile açılır/kapanır.
5007
+ DM'ler ajanların agent_dm araç çağrılarından gelir; çiftlere gruplanır. */
5008
+
5009
+ const dmState = {
5010
+ dms: [], // { at, from, fromTitle, to, toTitle, text }
5011
+ open: new Set(), // açık thread key'leri
5012
+ };
5013
+
5014
+ function dmPairOf(dm) {
5015
+ const a = String(dm.from || '');
5016
+ const b = String(dm.to || '');
5017
+ return a < b ? a + '|' + b : b + '|' + a;
5018
+ }
5019
+
5020
+ function dmPairTitle(dm) {
5021
+ const a = String(dm.fromTitle || 'Ajan');
5022
+ const b = String(dm.toTitle || 'Ajan');
5023
+ return a < b ? a + ' ↔ ' + b : b + ' ↔ ' + a;
5024
+ }
5025
+
5026
+ function dmTime(iso) {
5027
+ const d = new Date(iso || '');
5028
+ if (!d.getTime()) return '';
5029
+ return d.toLocaleTimeString('tr-TR', { hour: '2-digit', minute: '2-digit' });
5030
+ }
5031
+
5032
+ function renderDmRail() {
5033
+ const list = els.dmRailList;
5034
+ if (!list) return;
5035
+ if (!dmState.dms.length) {
5036
+ list.innerHTML = '<div class="dm-empty">Ajanlar arasında DM yok.<br>Ajanlar agent_dm aracıyla birbirine mesaj attıkça burada görünür.</div>';
5037
+ return;
5038
+ }
5039
+ /* çiftlere grupla (kronolojik) — en yeni thread üstte */
5040
+ const threads = new Map();
5041
+ for (const dm of dmState.dms) {
5042
+ const key = dmPairOf(dm);
5043
+ if (!threads.has(key)) threads.set(key, []);
5044
+ threads.get(key).push(dm);
5045
+ }
5046
+ const rows = [...threads.entries()].sort(
5047
+ (x, y) => new Date(y[1][y[1].length - 1].at) - new Date(x[1][x[1].length - 1].at)
5048
+ );
5049
+ list.innerHTML = '';
5050
+ for (const [key, msgs] of rows) {
5051
+ const open = dmState.open.has(key);
5052
+ const last = msgs[msgs.length - 1];
5053
+ const el = document.createElement('div');
5054
+ el.className = 'dmt' + (open ? ' open' : '');
5055
+ el.innerHTML =
5056
+ '<div class="dmt-head">' +
5057
+ `<span class="dmt-title">${escapeHtml(dmPairTitle(last))}</span>` +
5058
+ `<span class="dmt-count">${msgs.length}</span>` +
5059
+ '</div>' +
5060
+ `<div class="dmt-last">${escapeHtml(String(last.text || '').replace(/\s+/g, ' ').slice(0, 90))} · ${dmTime(last.at)}</div>` +
5061
+ '<div class="dmt-msgs">' +
5062
+ msgs
5063
+ .map(
5064
+ (m) =>
5065
+ `<div class="dmt-msg"><span class="dm-who">${escapeHtml(String(m.fromTitle || 'Ajan'))} → ${escapeHtml(String(m.toTitle || 'Ajan'))}</span>` +
5066
+ `${escapeHtml(String(m.text || ''))}<span class="dm-at">${dmTime(m.at)}</span></div>`
5067
+ )
5068
+ .join('') +
5069
+ '</div>';
5070
+ el.addEventListener('click', () => {
5071
+ if (dmState.open.has(key)) dmState.open.delete(key);
5072
+ else dmState.open.add(key);
5073
+ renderDmRail();
5074
+ const again = els.dmRailList.querySelector('.dmt.open .dmt-msgs');
5075
+ if (again) again.scrollTop = again.scrollHeight;
5076
+ });
5077
+ list.appendChild(el);
5078
+ }
5079
+ }
5080
+
5081
+ async function refreshDmRail() {
5082
+ try {
5083
+ const dms = await beast.agentDmsList();
5084
+ if (Array.isArray(dms)) dmState.dms = dms;
5085
+ } catch {}
5086
+ renderDmRail();
5087
+ }
5088
+
5089
+ function toggleDmRail(hide) {
5090
+ const rail = $('#dmRail');
5091
+ if (!rail) return;
5092
+ rail.classList.toggle('dm-hidden', !!hide);
5093
+ if (els.dmBtn) els.dmBtn.classList.toggle('on', !hide);
5094
+ if (!hide) refreshDmRail();
5095
+ }
5096
+
5097
+ if (els.dmBtn) {
5098
+ els.dmBtn.addEventListener('click', () => {
5099
+ toggleDmRail(!$('#dmRail').classList.contains('dm-hidden'));
5100
+ });
5101
+ }
5102
+ if (els.dmClear) {
5103
+ els.dmClear.addEventListener('click', async () => {
5104
+ if (!(await uiConfirm('TÜM ajan DM geçmişi silinsin mi?', 'Sil'))) return;
5105
+ try { await beast.agentDmsClear(); } catch {}
5106
+ dmState.dms = [];
5107
+ renderDmRail();
5108
+ toast('Ajan DM geçmişi silindi');
5109
+ });
5110
+ }
5111
+
5002
5112
  function renderAgentsPane() {
5003
5113
  const pane = $('#tab-agents');
5004
5114
  if (!pane) return;
@@ -5143,6 +5253,20 @@ function onEvent(ev) {
5143
5253
  scheduleAgentsRender();
5144
5254
  return;
5145
5255
  }
5256
+ /* AJAN DM: ajanlar arası mesaj — panel açıksa canlı düşer */
5257
+ if (ev.type === 'agent-dm') {
5258
+ dmState.dms.push({
5259
+ at: ev.at,
5260
+ from: ev.from,
5261
+ fromTitle: ev.fromTitle,
5262
+ to: ev.to,
5263
+ toTitle: ev.toTitle,
5264
+ text: ev.text,
5265
+ });
5266
+ if (dmState.dms.length > 400) dmState.dms.splice(0, dmState.dms.length - 400);
5267
+ if ($('#dmRail') && !$('#dmRail').classList.contains('dm-hidden')) renderDmRail();
5268
+ return;
5269
+ }
5146
5270
  /* OFFLINE MESAJ KUYRUĞU: bağlantı + kuyruk olayları (sessionId filtresinden önce) */
5147
5271
  if (ev.type === 'net' || ev.type === 'netQueue') { onNetEvent(ev); return; }
5148
5272
  /* Sandbox klonlama progress'i (sessionId'siz global olay) */
@@ -6066,6 +6190,8 @@ const SLASH_COMMANDS = [
6066
6190
  { cmd: '/notify', desc: 'hata maili aç/kapa · /notify on|off' },
6067
6191
  { cmd: '/think ', desc: 'düşünme seviyesi · /think 0-5 (0 kapalı)' },
6068
6192
  { cmd: '/clear', desc: 'oturum geçmişini GERÇEKTEN sil (kod korunur)' },
6193
+ { cmd: '/autodel', desc: 'tüm otomatik hatırlatmaları sil · /autodel all: cron dahil hepsi' },
6194
+ { cmd: '/deltodo', desc: 'todo listesini temizle · /deltodo all: tüm oturumlar' },
6069
6195
  { cmd: '/start', desc: 'durdurulan servisleri devam ettir' },
6070
6196
  { cmd: '/rule ', desc: 'kalıcı kural ekle' },
6071
6197
  { cmd: '/rules', desc: 'kalıcı kuralları listele' },
@@ -8342,7 +8468,17 @@ function finRenderSymbols(list) {
8342
8468
  row.innerHTML =
8343
8469
  '<span class="fs-name">' + (s.symbol || '?') + '</span>' +
8344
8470
  '<span class="fs-price ' + cls + '">' + bid.toFixed(digits) + '</span>' +
8345
- '<span class="fs-spread">sp ' + (s.spread != null ? s.spread : '—') + '</span>';
8471
+ '<span class="fs-spread">sp ' + (s.spread != null ? s.spread : '—') + '</span>' +
8472
+ '<button class="fs-agent" title="' + escapeHtml(s.symbol || '') + ' için AYRI finance ajanı başlat (paralel koşar)">&#9654;&#xFE0E;</button>';
8473
+ const spawnBtn = row.querySelector('.fs-agent');
8474
+ if (spawnBtn) {
8475
+ spawnBtn.addEventListener('click', async (e) => {
8476
+ e.stopPropagation();
8477
+ const r = await beast.financeAgentSpawn(s.symbol).catch((err) => ({ ok: false, error: String((err && err.message) || err) }));
8478
+ if (r && r.ok) toast(s.symbol + ' için ajan başladı — Paralel Ajanlar panelinde izle');
8479
+ else toast((r && r.error) || 'Ajan başlatılamadı');
8480
+ });
8481
+ }
8346
8482
  els.finSymList.appendChild(row);
8347
8483
  }
8348
8484
  }
@@ -2072,6 +2072,140 @@ body.browser-open #settingsDialog { width: min(70vw, calc(100vw - var(--bw, 480p
2072
2072
  letter-spacing: 0.5px;
2073
2073
  }
2074
2074
 
2075
+ /* ---------- sağ sütun 2: AJAN DM (ajanlar arası konuşma) ---------- */
2076
+
2077
+ #dmRail {
2078
+ flex: none;
2079
+ width: 270px;
2080
+ min-width: 270px;
2081
+ min-height: 0;
2082
+ background: var(--panel);
2083
+ border-left: 1px solid var(--border);
2084
+ display: flex;
2085
+ flex-direction: column;
2086
+ }
2087
+ #dmRail.dm-hidden { display: none; }
2088
+
2089
+ #dmRailHead {
2090
+ padding: 12px 12px 10px 16px;
2091
+ font-weight: 800;
2092
+ letter-spacing: 1px;
2093
+ font-size: 13px;
2094
+ color: var(--text);
2095
+ display: flex;
2096
+ align-items: center;
2097
+ gap: 8px;
2098
+ }
2099
+ /* rail gizliyken DM sütunu sağ kenara yaslanır — pencere butonları alanı */
2100
+ body.rail-hidden #dmRailHead { padding-right: 148px; }
2101
+ #dmRailTitle { flex: 1; text-align: left; }
2102
+ #dmClear {
2103
+ flex: none;
2104
+ display: inline-flex;
2105
+ align-items: center;
2106
+ justify-content: center;
2107
+ width: 24px;
2108
+ height: 24px;
2109
+ background: none;
2110
+ border: none;
2111
+ border-radius: 6px;
2112
+ cursor: pointer;
2113
+ color: #111;
2114
+ }
2115
+ #dmClear:hover { background: rgba(0, 0, 0, 0.08); }
2116
+ #dmClear svg { display: block; }
2117
+
2118
+ #dmRailList {
2119
+ flex: 1 1 auto;
2120
+ min-height: 0;
2121
+ overflow-y: auto;
2122
+ overflow-x: hidden;
2123
+ overscroll-behavior: contain;
2124
+ padding: 4px 8px;
2125
+ }
2126
+ .dm-empty {
2127
+ color: var(--muted);
2128
+ font-size: 11.5px;
2129
+ text-align: center;
2130
+ padding: 18px 10px;
2131
+ line-height: 1.5;
2132
+ }
2133
+ .dmt {
2134
+ margin-bottom: 6px;
2135
+ border: 1px solid var(--border);
2136
+ border-radius: 8px;
2137
+ padding: 7px 9px;
2138
+ cursor: pointer;
2139
+ color: var(--muted);
2140
+ min-width: 0;
2141
+ overflow: hidden;
2142
+ }
2143
+ .dmt:hover, .dmt.open { background: var(--panel2); color: var(--text); }
2144
+ .dmt-head { display: flex; align-items: center; gap: 6px; min-width: 0; }
2145
+ .dmt-title {
2146
+ flex: 1; min-width: 0;
2147
+ font-size: 12.5px; font-weight: 700;
2148
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
2149
+ }
2150
+ .dmt-count {
2151
+ flex: none; font-size: 10.5px;
2152
+ background: var(--accent-dim); color: var(--accent);
2153
+ border-radius: 8px; padding: 1px 7px; font-weight: 700;
2154
+ }
2155
+ .dmt-last {
2156
+ font-size: 11px; margin-top: 4px;
2157
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
2158
+ }
2159
+ .dmt-msgs {
2160
+ display: none;
2161
+ margin-top: 6px;
2162
+ max-height: 300px; overflow-y: auto;
2163
+ display: none;
2164
+ flex-direction: column;
2165
+ gap: 5px;
2166
+ }
2167
+ .dmt.open .dmt-msgs { display: flex; }
2168
+ .dmt-msg {
2169
+ font-size: 11px; line-height: 1.5;
2170
+ background: var(--panel2);
2171
+ border-radius: 6px;
2172
+ padding: 5px 7px;
2173
+ word-break: break-word;
2174
+ }
2175
+ .dmt-msg .dm-who {
2176
+ display: block;
2177
+ font-weight: 800;
2178
+ font-size: 10px;
2179
+ letter-spacing: 0.4px;
2180
+ color: var(--accent);
2181
+ margin-bottom: 2px;
2182
+ }
2183
+ .dmt-msg .dm-at {
2184
+ display: block;
2185
+ font-size: 9.5px;
2186
+ color: var(--muted);
2187
+ margin-top: 2px;
2188
+ }
2189
+
2190
+ /* PİYASA satırındaki ▶ ajan butonu */
2191
+ .fin-sym { position: relative; }
2192
+ .fin-sym .fs-agent {
2193
+ position: absolute;
2194
+ right: 4px;
2195
+ top: 50%;
2196
+ transform: translateY(-50%);
2197
+ border: none;
2198
+ background: none;
2199
+ color: var(--muted);
2200
+ cursor: pointer;
2201
+ font-size: 11px;
2202
+ padding: 2px 4px;
2203
+ border-radius: 4px;
2204
+ visibility: hidden;
2205
+ }
2206
+ .fin-sym:hover .fs-agent { visibility: visible; color: var(--accent); }
2207
+ .fin-sym .fs-agent:hover { background: var(--accent-dim); }
2208
+
2075
2209
  /* ---------- BOT SİSTEMİ (sol panel alt bölümü + bot sayfası) ---------- */
2076
2210
 
2077
2211
  /* üst bardaki aktif bot rozeti */