beast-agent 2.17.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/src/main.js CHANGED
@@ -1106,6 +1106,8 @@ function waSlashHelp() {
1106
1106
  '• */usage* – bugünkü kullanım',
1107
1107
  '• */backup* – tüm veriyi ŞİFRELİ yedekle (Beast Kodu imzalı, Masaüstü\\Beast-Backups)',
1108
1108
  '• */status* – bağlantı ve servis durumu',
1109
+ '• */autodel* – tüm otomatik hatırlatmaları (bayat/eski dahil) tek komutla sil · */autodel all*: cron görevleri dahil hepsi',
1110
+ '• */deltodo* – bu oturumun todo listesini temizle · */deltodo all*: tüm oturumların todoları',
1109
1111
  '',
1110
1112
  'Gruplarda beni @mention ile çağır; ardışık mesajlarını tek cevapta birleştiririm.',
1111
1113
  ].join('\n');
@@ -1646,6 +1648,58 @@ async function tryWaSlash(jid, rawText, senderNum, payload0) {
1646
1648
  } else {
1647
1649
  out = 'Kullanım: `/cron list` · `/cron clear` (hepsini sil) · `/cron del <id>`';
1648
1650
  }
1651
+ } else if (cmd === 'autodel') {
1652
+ /* /autodel [all] — TÜM otomatik hatırlatmaları tek komutla sil.
1653
+ Bayat/eski kayıtlar dahil: kind=reminder + eski isim/prompt önekleri eşleşir.
1654
+ /autodel all → cron görevleri dahil her şey (=/cron clear) */
1655
+ const sub = String(arg || '').toLowerCase().trim();
1656
+ if (sub === 'all' || sub === 'hepsi' || sub === 'hepsini') {
1657
+ const r = cron.clearAll();
1658
+ cronEmit();
1659
+ out = r.ok
1660
+ ? `*Tüm zamanlanmış görevler silindi* (${r.count} adet).`
1661
+ : 'Temizlenemedi.';
1662
+ } else {
1663
+ const r = cron.removeIf(isReminderJob);
1664
+ cronEmit();
1665
+ const left = cron.list().filter((j) => j.enabled).length;
1666
+ out = r.count
1667
+ ? `*${r.count} otomatik hatırlatma silindi.* Kalan aktif görev: ${left}`
1668
+ : 'Silinecek otomatik hatırlatma yok — zaten temiz.';
1669
+ }
1670
+ } else if (cmd === 'deltodo') {
1671
+ /* /deltodo [all] — todo listelerini temizler: arg=all → TÜM oturumlar;
1672
+ yoksa yalnız bu sohbetin oturumu. Panelden de düşsün diye 'todos'
1673
+ olayı emit edilir (engine.clearTodos). */
1674
+ const sub = String(arg || '').toLowerCase().trim();
1675
+ if (sub === 'all' || sub === 'hepsi' || sub === 'hepsini') {
1676
+ let n = 0;
1677
+ let items = 0;
1678
+ try {
1679
+ for (const v of engine.listSessions()) {
1680
+ const r = engine.clearTodos(v.id);
1681
+ if (r && r.ok) {
1682
+ n++;
1683
+ items += r.count || 0;
1684
+ }
1685
+ }
1686
+ } catch {}
1687
+ out = n
1688
+ ? `*Tüm oturumların todoları temizlendi* (${n} oturum, ${items} madde).`
1689
+ : 'Temizlenecek todo yok.';
1690
+ } else {
1691
+ const sid = waChats.get(jid) || waSingleSid || '';
1692
+ if (!sid) {
1693
+ out = 'Bu sohbette açık oturum yok — todo listesi de yok.';
1694
+ } else {
1695
+ const r = engine.clearTodos(sid);
1696
+ out = r.ok
1697
+ ? (r.count
1698
+ ? `*${r.count} todo temizlendi.* (tüm oturumlar için: /deltodo all)`
1699
+ : 'Todo listesi zaten boş. (tüm oturumlar için: /deltodo all)')
1700
+ : 'Oturum bulunamadı — todo listesi yok.';
1701
+ }
1702
+ }
1649
1703
  } else if (cmd === 'beastcode') {
1650
1704
  /* /beastcode [görev] — WhatsApp'tan UZAKTAN KODLAMA modu:
1651
1705
  masaüstünde GERÇEK Beast Code paneli açılır (IDE ekranı), sohbet
@@ -2084,6 +2138,22 @@ async function documentToTextAsync(media) {
2084
2138
 
2085
2139
  /* ---------- WA hatırlatıcı ---------- */
2086
2140
 
2141
+ /* Hatırlatma job'ı mı? Yeni kayıtlarda kind='reminder' var; eski (bayat)
2142
+ kayıtlar isim/prompt önekinden tanınır — /autodel ve doğal dil silme
2143
+ bunu kullanır. */
2144
+ function isReminderJob(j) {
2145
+ if (!j || typeof j !== 'object') return false;
2146
+ if (j.kind === 'reminder') return true;
2147
+ const n = String(j.name || '');
2148
+ const p = String(j.prompt || '');
2149
+ return (
2150
+ /^Hatırlatma:/.test(n) ||
2151
+ /^Tekrarlı hatırlatma:/.test(n) ||
2152
+ p.startsWith('[HATIRLATMA ZAMANI]') ||
2153
+ p.startsWith('[TEKRARLI HATIRLATMA]')
2154
+ );
2155
+ }
2156
+
2087
2157
  function scheduleReminder({ when, message, sessionId, repeat }) {
2088
2158
  try {
2089
2159
  const msg = String(message || '').trim();
@@ -2097,6 +2167,7 @@ function scheduleReminder({ when, message, sessionId, repeat }) {
2097
2167
  name: 'Tekrarlı hatırlatma: ' + msg.slice(0, 40),
2098
2168
  schedule: s.schedule,
2099
2169
  prompt: `[TEKRARLI HATIRLATMA] Kullanıcıya şunu hatırlat: "${msg}". Kısaca ve nazikçe bildir.`,
2170
+ kind: 'reminder',
2100
2171
  sessionId: sessionId || undefined,
2101
2172
  });
2102
2173
  if (!r.ok) return r;
@@ -2111,6 +2182,7 @@ function scheduleReminder({ when, message, sessionId, repeat }) {
2111
2182
  name: 'Hatırlatma: ' + msg.slice(0, 40),
2112
2183
  schedule: cronExpr,
2113
2184
  prompt: `[HATIRLATMA ZAMANI] Kullanıcıya şunu hatırlat: "${msg}". Kısaca ve nazikçe bildir.`,
2185
+ kind: 'reminder',
2114
2186
  once: true,
2115
2187
  sessionId: sessionId || undefined,
2116
2188
  });
@@ -2291,6 +2363,23 @@ async function handleWaIncoming(jid, payload, senderNum) {
2291
2363
  return;
2292
2364
  }
2293
2365
 
2366
+ /* "hatırlatmaları sil" / "alarm temizle" / "reminders kaldır" → yalnızca
2367
+ otomatik hatırlatmalar silinir (cron görevlerine dokunulmaz). */
2368
+ if (
2369
+ /hat[iı]rlat|alarm|remind/.test(lc) &&
2370
+ /(sil|sıl|temizle|kaldır|iptal|sıfırla|kapat)/.test(lc)
2371
+ ) {
2372
+ const r = cron.removeIf(isReminderJob);
2373
+ cronEmit();
2374
+ await sendWaSafe(
2375
+ jid,
2376
+ r.count
2377
+ ? `*${r.count} otomatik hatırlatma silindi.* Zamanlanmış kalan görev: ${cron.list().filter((j) => j.enabled).length}`
2378
+ : 'Silinecek otomatik hatırlatma yok — zaten temiz.'
2379
+ ).catch(() => {});
2380
+ return;
2381
+ }
2382
+
2294
2383
  if (isGroup) {
2295
2384
  const g = settings.waGroups || {};
2296
2385
  if (!g.enabled) return;
@@ -3307,11 +3396,11 @@ function reloadBackend() {
3307
3396
  }
3308
3397
  /* CRON → TÜM AKTİF ENTEGRASYONLAR: cron işi bittiğinde cevap (ya da
3309
3398
  hata) sahibin bağlı olduğu her kanala yansıtılır — WA + Telegram +
3310
- Discord. Aynı kanal hem cron oturumuna bağlıysa TEK cevap alır. */
3311
- if ((ev.type === 'done' || ev.type === 'error') && cronAnswerPending.has(String(ev.sessionId))) {
3312
- const cjob = cronAnswerPending.get(String(ev.sessionId));
3313
- cronAnswerPending.delete(String(ev.sessionId));
3314
- if (!ev.aborted) {
3399
+ Discord. Aynı kanal hem cron oturumuna bağlıysa TEK cevap alır.
3400
+ Bayat (TTL aşımı) bekleme kaydı Take ile düşer, yansıtılmaz. */
3401
+ if (ev.type === 'done' || ev.type === 'error') {
3402
+ const cjob = cronAnswerPendingTake(ev.sessionId);
3403
+ if (cjob && !ev.aborted) {
3315
3404
  (async () => {
3316
3405
  try {
3317
3406
  let txt = '';
@@ -5011,6 +5100,57 @@ ipcMain.handle('agent:send', (_e, { sessionId, text }) => {
5011
5100
  desktopEcho(sessionId, '/help', desktopSlashHelp());
5012
5101
  return true;
5013
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
+ }
5014
5154
  if (t === '/rules') {
5015
5155
  const rs = memory.listRules();
5016
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>**');
@@ -5205,6 +5345,8 @@ function desktopSlashHelp() {
5205
5345
  '**/think 0-5** – düşünme seviyesi (0 kapalı · 5 max)',
5206
5346
  '**/agent [isim]** – özel ajan bağla / listele (%APPDATA%\\beast\\agents\\*.md)',
5207
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ı',
5208
5350
  '**/notes** – bu oturumun notlarını göster',
5209
5351
  '**/rule <metin>** – kalıcı kural ekle · **/rules** – listele',
5210
5352
  '**/notify on|off** – hata mail bildirimini aç/kapa',
@@ -5738,9 +5880,18 @@ ipcMain.handle('memory:get', () => memory.loadAll());
5738
5880
  /* paralel ajanlar: canlı izleme (#14) */
5739
5881
  ipcMain.handle('agents:list', () => engine.listBgJobs());
5740
5882
  ipcMain.handle('agents:detail', (_e, id) => engine.bgDetail(id));
5741
- ipcMain.handle('agents:cancel', (_e, id) => ({
5742
- ok: engine.interrupt(String(id || ''), 'kullanıcı Paralel Ajanlar panelinden (×) iptal etti'),
5743
- }));
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 }));
5744
5895
  ipcMain.handle('ceo:get', () => !!engine.ceoMode);
5745
5896
  ipcMain.handle('ceo:set', (_e, v) => {
5746
5897
  settings.ceoMode = !!v;
@@ -6519,8 +6670,40 @@ function cronEmit() {
6519
6670
 
6520
6671
  /* Cron cevabı bekleme haritası: sid -> job. İş 'done' olunca cevap
6521
6672
  SAHİBİN bağlı olduğu TÜM AKTİF entegrasyonlara yansıtılır (kullanıcı
6522
- hangi kanaldan ajanla iletişimde belli değil). */
6673
+ hangi kanaldan ajanla iletişimde belli değil). Kayıtlar TTL'li —
6674
+ done/error hiç gelmezse bayat bekleme sonraki done'a yansıtılmaz. */
6523
6675
  const cronAnswerPending = new Map();
6676
+ const cronAnswerPendingAt = new Map();
6677
+ const CRON_ANSWER_TTL_MS = 30 * 60 * 1000;
6678
+
6679
+ function cronAnswerPendingSet(sid, job) {
6680
+ cronAnswerPendingSweep();
6681
+ cronAnswerPending.set(String(sid), job);
6682
+ cronAnswerPendingAt.set(String(sid), Date.now());
6683
+ }
6684
+
6685
+ function cronAnswerPendingDrop(sid) {
6686
+ cronAnswerPending.delete(String(sid));
6687
+ cronAnswerPendingAt.delete(String(sid));
6688
+ }
6689
+
6690
+ /* Bayat kayıt (TTL aşımı) haritalardan temizlenir */
6691
+ function cronAnswerPendingSweep() {
6692
+ const now = Date.now();
6693
+ for (const [k, at] of cronAnswerPendingAt) {
6694
+ if (now - at > CRON_ANSWER_TTL_MS) cronAnswerPendingDrop(k);
6695
+ }
6696
+ }
6697
+
6698
+ /* Kaydı okuyup düşürür; bayat kayıt ise null döner (yansıtma yok) */
6699
+ function cronAnswerPendingTake(sid) {
6700
+ const k = String(sid);
6701
+ const job = cronAnswerPending.get(k);
6702
+ const at = cronAnswerPendingAt.get(k) || 0;
6703
+ cronAnswerPendingDrop(k);
6704
+ if (!job || Date.now() - at > CRON_ANSWER_TTL_MS) return null;
6705
+ return job;
6706
+ }
6524
6707
 
6525
6708
  /* OTOMATİK YENİ SOHBET YOK: cron/izleyici/fallout tetiklendiğinde oturum
6526
6709
  seçimi — kayıtlı id'nin DOSYASI hâlâ duruyorsa O, değilse EN GÜNCEL
@@ -6600,11 +6783,11 @@ function cronFire(job) {
6600
6783
  if (sid !== String(job.sessionId || '')) {
6601
6784
  cron.update(job.id, { sessionId: sid });
6602
6785
  }
6603
- cronAnswerPending.set(String(sid), job);
6786
+ cronAnswerPendingSet(sid, job);
6604
6787
  const sent = engine.send(sid, {
6605
6788
  text: `[cron: ${job.name}]\n${job.prompt}`,
6606
6789
  });
6607
- if (!sent) cronAnswerPending.delete(String(sid)); // gönderilemedi — bayat bekleme bırakma
6790
+ if (!sent) cronAnswerPendingDrop(sid); // gönderilemedi — bayat bekleme bırakma
6608
6791
  } catch {}
6609
6792
  cronEmit();
6610
6793
  }
@@ -7124,6 +7307,9 @@ ipcMain.handle('watchers:toggle', (_e, id) => {
7124
7307
  watchers.patch(String(id), { enabled: !w.enabled });
7125
7308
  return { ok: true, watcher: watchers.get(String(id)) };
7126
7309
  });
7310
+ /* izleyici logları — ana depodan ayrı watcher-logs.json üzerinden */
7311
+ ipcMain.handle('watchers:logs', () => watchers.logsAll());
7312
+ ipcMain.handle('watchers:logsClear', (_e, id) => watchers.logClear(id ? String(id) : null));
7127
7313
  ipcMain.handle('cron:add', (_e, job) => {
7128
7314
  const r = cron.add(job || {});
7129
7315
  cronEmit();
@@ -8090,13 +8276,13 @@ const financetools = require('./agent/financetools');
8090
8276
 
8091
8277
  const financeState = {
8092
8278
  mode: false, /* chat oturumları finance bayrağı alıyor mu */
8093
- traderSid: null,
8279
+ traderSid: null, /* ANA trader (panel kartı buna bağlı) */
8094
8280
  traderOn: false,
8095
- traderTimer: null,
8096
8281
  traderRounds: 0,
8097
8282
  lastRoundAt: 0,
8098
8283
  installing: false,
8099
8284
  installTried: false,
8285
+ agents: new Map(), /* sid -> { symbols, main, round, timer } — AYNI ANDA koşan finance ajanları */
8100
8286
  };
8101
8287
 
8102
8288
  function financeDir() {
@@ -8108,7 +8294,10 @@ function financeDir() {
8108
8294
  function finCfg() {
8109
8295
  if (!settings.finance || typeof settings.finance !== 'object') settings.finance = {};
8110
8296
  const f = settings.finance;
8111
- 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 */
8112
8301
  if (!Number(f.intervalSec)) f.intervalSec = 120;
8113
8302
  if (!Number(f.maxLot)) f.maxLot = 0.1;
8114
8303
  if (f.maxPositions == null) f.maxPositions = 3;
@@ -8210,97 +8399,256 @@ mt5bridge.on('log', (m) => {
8210
8399
  financeLog('[mt5] ' + line.slice(0, 300));
8211
8400
  });
8212
8401
 
8213
- /* ---------- 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. */
8214
8408
 
8215
- function finTraderSession() {
8216
- let sid = financeState.traderSid;
8217
- if (sid) {
8218
- try {
8219
- const s = engine.cache.get(sid);
8220
- if (s) return s;
8221
- } catch {}
8222
- 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
+ }
8223
8419
  }
8224
8420
  const s = engine._load(engine.createSession().id);
8225
8421
  s.messages = s.messages || [];
8226
- 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 */
8227
8425
  try {
8228
8426
  fs.appendFileSync(
8229
8427
  engine._file(s.id),
8230
- 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'
8231
8429
  );
8232
8430
  } catch {}
8233
8431
  engine.cache.set(s.id, s);
8234
- engine.markFinance(s.id, true); /* kalıcı finance etiketi (trader rolüyle) */
8235
- s.workspace = financeDir(); /* trader kendi klasöründe çalışır */
8236
- 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;
8237
8435
  return s;
8238
8436
  }
8239
8437
 
8240
- 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) {
8241
8469
  const f = finCfg();
8242
8470
  s.finance = true;
8243
8471
  s.financeTrader = true;
8244
8472
  s.financeAuto = f.allowTrading === true;
8245
- s.financeSymbols = f.symbols;
8473
+ s.financeSymbols = Array.isArray(symbolsOverride) && symbolsOverride.length ? symbolsOverride : f.symbols;
8246
8474
  s.financeStrategy = String(f.strategy || '');
8247
8475
  s.financeLimits = { maxLot: f.maxLot, maxPositions: f.maxPositions };
8248
8476
  engine.cache.set(String(s.id), s);
8249
8477
  }
8250
8478
 
8251
- function finTraderBrief() {
8479
+ /* Yeni finance ajanı aç (ana trader YA DA sembol işçisi) */
8480
+ function finAgentCreate(symbols, isMain) {
8252
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) {
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';
8253
8525
  return [
8254
- 'Beast Finance TRADER başlatıldı — ilk tur: strateji çerçeveni kur ve piyasa taramasını yap.',
8255
- `İ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ç)'}`,
8256
8528
  `Tur aralığı: ${f.intervalSec} sn · Max lot: ${f.maxLot} · Max eşzamanlı pozisyon: ${f.maxPositions}`,
8257
8529
  f.allowTrading
8258
8530
  ? 'Otomatik işlem AÇIK: mt5_trade/mt5_close/mt5_modify/mt5_pending kullanabilirsin (limitler sistemce zorlanır).'
8259
8531
  : 'Otomatik işlem KAPALI: SADECE analiz + net işlem önerileri yaz (sembol, yön, giriş, SL, TP, sebep); işlem açma.',
8260
8532
  f.strategy ? `Sahibinin strateji notu: ${f.strategy}` : 'Strateji notu yok: trend + destek/direnç + momentum ile temel okuma yap.',
8261
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).',
8262
8535
  ].join('\n');
8263
8536
  }
8264
8537
 
8265
- function finTraderRound() {
8266
- if (!financeState.traderOn) return;
8267
- if (!engine) return;
8268
- const s = finTraderSession();
8269
- finApplyTraderFields(s);
8270
- 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)) {
8271
8595
  /* hâlâ çalışıyor — done eventinde tekrar planlanır */
8272
8596
  return;
8273
8597
  }
8274
- financeState.traderRounds += 1;
8598
+ agent.round += 1;
8599
+ if (agent.main) financeState.traderRounds = agent.round;
8275
8600
  const f = finCfg();
8601
+ try {
8602
+ const s = engine.cache.get(String(sid));
8603
+ if (s) finApplyTraderFields(s, agent.symbols);
8604
+ } catch {}
8276
8605
  const auto = f.allowTrading
8277
8606
  ? 'İşlem açabilirsin — limitlere uy, SL\u2019siz pozisyon bırakma.'
8278
8607
  : 'Otomatik işlem KAPALI — sadece analiz + öneri.';
8279
- const ok = engine.send(
8280
- s.id,
8281
- `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.`,
8282
- { userAction: false }
8283
- );
8284
- if (ok) {
8285
- financeState.lastRoundAt = Date.now();
8286
- finPush('trader', { state: 'running', round: financeState.traderRounds });
8287
- } else {
8288
- /* stop kapısı/yoğunluk — yarım dakika sonra sessizce tekrar dene */
8289
- clearTimeout(financeState.traderTimer);
8290
- 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;
8291
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(''));
8292
8631
  }
8293
8632
 
8633
+ /* Tur/durum sonları: sürekli ajan döngüsünü besle; kullanıcı iptalinde kapat */
8294
8634
  function finFlushOnDone(ev) {
8295
8635
  if (!ev || (ev.type !== 'done' && ev.type !== 'error')) return;
8296
- if (String(ev.sessionId || '') !== String(financeState.traderSid || '')) return;
8297
- if (!financeState.traderOn) return;
8298
- 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);
8299
8645
  const f = finCfg();
8300
8646
  const iv = Math.max(30, Number(f.intervalSec) || 120) * 1000;
8301
8647
  financeState.lastRoundAt = Date.now();
8302
- finPush('trader', { state: 'idle', round: financeState.traderRounds, nextInSec: iv / 1000 });
8303
- 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);
8304
8652
  }
8305
8653
 
8306
8654
  ipcMain.handle('finance:state', async () => {
@@ -8407,36 +8755,76 @@ ipcMain.handle('finance:trader:start', async () => {
8407
8755
  if (!engine) return { ok: false, error: 'ajan hazır değil' };
8408
8756
  const f = finCfg();
8409
8757
  if (!engine.publicState().hasModel && !f.traderSel) return { ok: false, error: 'model yok — Ayarlar → Provider' };
8410
- const s = finTraderSession();
8411
- finApplyTraderFields(s);
8412
- 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 {}
8413
8774
  financeState.traderOn = true;
8414
- financeState.traderRounds = 0;
8415
8775
  try { engine.clearStop(); } catch {}
8416
- const ok = engine.send(s.id, finTraderBrief(), { userAction: true });
8776
+ const ok = engine.send(mainSid, finTraderBrief(mainAgent), { userAction: true });
8417
8777
  if (!ok) {
8418
8778
  financeState.traderOn = false;
8419
8779
  return { ok: false, error: 'trader oturumu meşgul — birkaç saniye sonra tekrar dene' };
8420
8780
  }
8421
8781
  financeState.lastRoundAt = Date.now();
8422
8782
  financeLog('[trader] başlatıldı (model: ' + (f.traderSel || 'genel aktif model') + ')');
8423
- finPush('trader', { state: 'running', round: 0 });
8424
- return { ok: true, sid: s.id };
8783
+ finPush('trader', { state: 'running', round: mainAgent.round });
8784
+ return { ok: true, sid: mainSid };
8425
8785
  });
8426
8786
 
8427
8787
  ipcMain.handle('finance:trader:stop', async () => {
8428
8788
  financeState.traderOn = false;
8429
- clearTimeout(financeState.traderTimer);
8430
- financeState.traderTimer = null;
8431
- let interrupted = false;
8432
- if (financeState.traderSid && engine && engine.isBusy(financeState.traderSid)) {
8433
- 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
+ }
8434
8796
  }
8797
+ const stopped = mainSid ? finAgentStop(mainSid, 'kullanıcı Beast Finance trader ajanını durdurdu') : false;
8798
+ const interrupted = !!stopped;
8435
8799
  financeLog('[trader] durduruldu');
8436
8800
  finPush('trader', { state: 'stopped' });
8437
8801
  return { ok: true, interrupted };
8438
8802
  });
8439
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
+
8440
8828
  ipcMain.handle('finance:connect', async () => {
8441
8829
  mt5bridge.stop();
8442
8830
  const st = financeEnsureBridge();