beast-agent 2.14.0 → 2.15.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
@@ -26,7 +26,6 @@ const watchers = require('./agent/watchers');
26
26
  const usageMod = require('./agent/usage');
27
27
  const bus = require('./agent/bus');
28
28
  const computeruse = require('./agent/computeruse');
29
- const android = require('./agent/android');
30
29
  const log = require('./agent/logger');
31
30
  const headroom = require('./agent/headroom');
32
31
  const QRCode = require('qrcode'); /* Expo Go QR (bc-expurl) — whatsapp ile aynı paket */
@@ -3107,6 +3106,7 @@ function reloadBackend() {
3107
3106
  bcFlushOnDone(ev); /* Beast Code kuyruğunu iş bitiminde boşalt */
3108
3107
  stFlushOnDone(ev); /* Beast Studio kuyruğunu iş bitiminde boşalt */
3109
3108
  sbFlushOnDone(ev); /* Sandbox kuyruğunu iş bitiminde boşalt */
3109
+ finFlushOnDone(ev); /* Finance trader döngüsü: iş bitince sıradaki turu planla */
3110
3110
  /* BC canlı önizleme: ajan bir dev server başlattıysa adresi yakala —
3111
3111
  preview butonu ve otomatik açılış DAİMA bu sunucuyu öncelikli kullanır */
3112
3112
  if (ev.type === 'bc-preview' && /^https?:\/\/(?:localhost|127\.0\.0\.1)(?::\d{2,5})?/i.test(String(ev.url || ''))) {
@@ -4904,6 +4904,13 @@ ipcMain.handle('sessions:create', () => {
4904
4904
  engine.setSessionTools(v.id, null);
4905
4905
  }
4906
4906
  engine.setSessionModel(v.id, b && !b.admin ? (b.model || null) : null);
4907
+ /* Beast Finance modu açıkken açılan sohbet finance izolasyonuna girer */
4908
+ if (financeState.mode) {
4909
+ try {
4910
+ engine.markFinance(v.id, false);
4911
+ v.finance = true;
4912
+ } catch {}
4913
+ }
4907
4914
  return v;
4908
4915
  });
4909
4916
  ipcMain.handle('sessions:open', (_e, id) => engine.openSession(id));
@@ -4932,6 +4939,25 @@ ipcMain.handle('agent:send', (_e, { sessionId, text }) => {
4932
4939
  }
4933
4940
  }
4934
4941
  } catch {}
4942
+ /* BEAST FINANCE: finance modu açıkken bu sohbet finance listesine girer ve
4943
+ mt5_* araçlarını görür (botlar hariç); mod kapalıysa etiket kaldırılır */
4944
+ try {
4945
+ if (sessionId) {
4946
+ const fsid = String(sessionId);
4947
+ let fsess = engine.cache.get(fsid);
4948
+ if (!fsess) { try { fsess = engine._load(fsid); } catch {} }
4949
+ if (fsess && !fsess.botId && !fsess.bgJob) {
4950
+ if (financeState.mode) {
4951
+ if (!fsess.finance) engine.markFinance(fsid, false);
4952
+ finApplyTraderFields(fsess);
4953
+ fsess.financeTrader = false; /* chat copilot'ı — trader değil */
4954
+ engine.cache.set(fsid, fsess);
4955
+ } else if (fsess.finance) {
4956
+ engine.unmarkFinance(fsid); /* normal modda yazınca normal sohbete döner */
4957
+ }
4958
+ }
4959
+ }
4960
+ } catch {}
4935
4961
  if (t === '/stop' || t === '/start') {
4936
4962
  handleGlobalStopStart(sessionId, t);
4937
4963
  return true;
@@ -6310,29 +6336,6 @@ ipcMain.handle('install:status', async () => {
6310
6336
  });
6311
6337
  }
6312
6338
 
6313
- /* 10) Android Emülatörü — opsiyonel; varsa Beast Code mobil uygulamaları emülatörde açar */
6314
- {
6315
- const st = android.probe();
6316
- let state = 'optional';
6317
- if (st.sdk) state = 'ok';
6318
- else if (st.installing) state = 'loading';
6319
- if (st.failed) state = 'failed';
6320
- rows.push({
6321
- id: 'android',
6322
- name: 'Android Emülatörü — mobil uygulamalar emülatörde çalışır',
6323
- state,
6324
- canInstall: true,
6325
- detail: st.installing
6326
- ? (st.stage || 'arka planda kuruluyor — JDK + SDK + sistem imajı (~1.5GB, internete göre 5-20 dk)')
6327
- : st.failed
6328
- ? (st.error || 'kurulum başarısız')
6329
- : st.sdk
6330
- ? 'hazır — SDK: ' + st.sdkPath
6331
- : 'kurulu değil — KUR ile arka planda kurulur (istekge bağlı)',
6332
- ...(st.installing ? pctFields('android') : {}),
6333
- });
6334
- }
6335
-
6336
6339
  return rows;
6337
6340
  });
6338
6341
 
@@ -6349,10 +6352,7 @@ ipcMain.handle('headroom:toggle', async (_e, on) => {
6349
6352
  ipcMain.handle('headroom:status', () => headroom.probe());
6350
6353
  ipcMain.handle('headroom:stats', () => headroom.stats());
6351
6354
 
6352
- /* ANDROID EMÜLATÖR (opsiyonel): durum + arka plan kurulum + AVD başlatma */
6353
- ipcMain.handle('android:status', async () => ({ ok: true, ...(await android.probe()), avds: await android.listAvds() }));
6354
- ipcMain.handle('android:install', () => android.autoInstall());
6355
- ipcMain.handle('android:startAvd', async (_e, name) => android.startAvd(name));
6355
+ /* ANDROID EMÜLATÖR: kaldırıldı — mobil önizleme tunnel (telefon QR) yoluyla yapılır */
6356
6356
 
6357
6357
  /* embedding modelini şimdi indir (mem0 arama yolu ısıtılır) */
6358
6358
  ipcMain.handle('embed:prefetch', () => {
@@ -7987,6 +7987,388 @@ function studioWatchStop() {
7987
7987
  if (studioWatchTimer) { clearTimeout(studioWatchTimer); studioWatchTimer = null; }
7988
7988
  }
7989
7989
 
7990
+ /* ---------------- Beast Finance (MT5) ----------------
7991
+ Studio/Sandbox ile AYRI dünya: solda sohbet geçmişi + izleyiciler AYNI kalır,
7992
+ ortada chat, sağda MT5 işlem paneli. İki ajan: (1) chat copilot'ı — aktif
7993
+ sohbet finance bayrağıyla mt5_* araçlarını görür; (2) TRADER — gizli engine
7994
+ oturumu, tur tur piyasa tarayıp (farklı API/model seçilebilir) işlem kovalar. */
7995
+ const mt5bridge = require('./mt5bridge');
7996
+ const financetools = require('./agent/financetools');
7997
+
7998
+ const financeState = {
7999
+ mode: false, /* chat oturumları finance bayrağı alıyor mu */
8000
+ traderSid: null,
8001
+ traderOn: false,
8002
+ traderTimer: null,
8003
+ traderRounds: 0,
8004
+ lastRoundAt: 0,
8005
+ installing: false,
8006
+ installTried: false,
8007
+ };
8008
+
8009
+ function financeDir() {
8010
+ const d = path.join(APP_DIR, 'finance');
8011
+ try { fs.mkdirSync(d, { recursive: true }); } catch {}
8012
+ return d;
8013
+ }
8014
+
8015
+ function finCfg() {
8016
+ if (!settings.finance || typeof settings.finance !== 'object') settings.finance = {};
8017
+ const f = settings.finance;
8018
+ if (!Array.isArray(f.symbols) || !f.symbols.length) f.symbols = ['EURUSD', 'XAUUSD', 'GBPUSD', 'BTCUSD'];
8019
+ if (!Number(f.intervalSec)) f.intervalSec = 120;
8020
+ if (!Number(f.maxLot)) f.maxLot = 0.1;
8021
+ if (f.maxPositions == null) f.maxPositions = 3;
8022
+ if (typeof f.allowTrading !== 'boolean') f.allowTrading = false;
8023
+ return f;
8024
+ }
8025
+
8026
+ function financeLog(line) {
8027
+ try {
8028
+ fs.appendFileSync(path.join(financeDir(), 'finance.log'), `[${new Date().toISOString()}] ${line}\n`);
8029
+ } catch {}
8030
+ finPush('log', { line });
8031
+ }
8032
+
8033
+ function finPush(fn, data) {
8034
+ try {
8035
+ if (win && !win.isDestroyed()) win.webContents.send('agent:event', { type: 'finance', fn, ...data });
8036
+ } catch {}
8037
+ }
8038
+
8039
+ /* financetools ayar + bildirim kancası */
8040
+ try {
8041
+ financetools.setConfig(() => finCfg());
8042
+ financetools.setNotify((entry) => {
8043
+ const k = String((entry && entry.kind) || '');
8044
+ const d = entry && entry.data ? entry.data : {};
8045
+ let line = '';
8046
+ if (k === 'trade') line = `İŞLEM AÇILDI: ${d.side} ${d.volume} ${d.symbol}`;
8047
+ else if (k === 'close') line = `POZİSYON KAPANDI: ticket ${d.ticket}${d.volume ? ' (' + d.volume + ' lot)' : ''}`;
8048
+ else if (k === 'modify') line = `SL/TP GÜNCELLENDİ: ticket ${d.ticket} (SL ${d.sl || 0} / TP ${d.tp || 0})`;
8049
+ else if (k === 'pending') line = `BEKLEYEN EMİR: ${d.type} ${d.volume} ${d.symbol}`;
8050
+ else if (k === 'cancel') line = `EMİR İPTAL: ticket ${d.ticket}`;
8051
+ if (line) {
8052
+ financeLog('[ajan] ' + line);
8053
+ finPush('trade', { line });
8054
+ }
8055
+ });
8056
+ } catch {}
8057
+
8058
+ function financeEnsureBridge() {
8059
+ const f = finCfg();
8060
+ const candidates = [String(f.pythonPath || '').trim(), 'python', 'py -3'].filter(Boolean);
8061
+ /* Beast gömülü Python runtime kuruluysa en başa ekle (makinede Python olmasa da köprü çalışır) */
8062
+ try {
8063
+ const t = require('./agent/tools');
8064
+ const emb = t.embeddedPythonExe();
8065
+ if (emb && fs.existsSync(emb) && !candidates.includes(emb)) candidates.unshift(emb);
8066
+ } catch {}
8067
+ mt5bridge.start({
8068
+ candidates,
8069
+ terminal: String(f.terminalPath || '').trim(),
8070
+ });
8071
+ return mt5bridge.status();
8072
+ }
8073
+
8074
+ /* MetaTrader5 paketi yoksa bir kez pip ile kurmayı dene */
8075
+ function financeTryInstall() {
8076
+ if (financeState.installing || (financeState.installTried && finCfg().autoInstall === false)) return;
8077
+ financeState.installing = true;
8078
+ financeState.installTried = true;
8079
+ financeLog('[MT5] MetaTrader5 python paketi kuruluyor…');
8080
+ const py = mt5bridge.status().python || 'python';
8081
+ /* 'py -3' gibi çok kelimeli kısa komutlar bölünür; tam yol tek exe'dir */
8082
+ const parts = /[\\/]/.test(py) ? [py] : py.split(/\s+/);
8083
+ const p = spawn(parts[0], [...parts.slice(1), '-m', 'pip', 'install', '--user', 'MetaTrader5'], { windowsHide: true });
8084
+ let tail = '';
8085
+ p.stdout.on('data', (c) => { tail = (tail + c).slice(-800); });
8086
+ p.stderr.on('data', (c) => { tail = (tail + c).slice(-800); });
8087
+ p.on('error', (e) => {
8088
+ financeState.installing = false;
8089
+ financeLog('[MT5] pip başlatılamadı: ' + String((e && e.message) || e));
8090
+ });
8091
+ p.on('exit', (code) => {
8092
+ financeState.installing = false;
8093
+ if (code === 0) {
8094
+ financeLog('[MT5] MetaTrader5 paketi kuruldu — köprü yeniden başlatılıyor');
8095
+ mt5bridge.stop();
8096
+ financeEnsureBridge();
8097
+ } else {
8098
+ financeLog('[MT5] kurulum başarısız (kod ' + code + ') ' + tail.slice(-300));
8099
+ }
8100
+ finPush('install', { installing: false, code });
8101
+ });
8102
+ }
8103
+
8104
+ mt5bridge.on('bridge', (m) => {
8105
+ if (m && m.connected) {
8106
+ financeLog('[MT5] terminal bağlı: ' + ((m.terminal && (m.terminal.name + ' @ ' + m.terminal.company)) || 'MT5'));
8107
+ if (m.account) financeLog(`[MT5] hesap ${m.account.login} · bakiye ${m.account.balance} ${m.account.currency}`);
8108
+ } else if (m && m.error) {
8109
+ financeLog('[MT5] bağlantı yok — ' + m.error);
8110
+ if (/paket/i.test(m.error)) financeTryInstall();
8111
+ }
8112
+ finPush('bridge', { connected: !!(m && m.connected), error: (m && m.error) || '' });
8113
+ });
8114
+ mt5bridge.on('log', (m) => {
8115
+ const line = String((m && m.line) || '');
8116
+ if (!line) return;
8117
+ financeLog('[mt5] ' + line.slice(0, 300));
8118
+ });
8119
+
8120
+ /* ---------- TRADER ajanı (gizli engine oturumu + tur döngüsü) ---------- */
8121
+
8122
+ function finTraderSession() {
8123
+ let sid = financeState.traderSid;
8124
+ if (sid) {
8125
+ try {
8126
+ const s = engine.cache.get(sid);
8127
+ if (s) return s;
8128
+ } catch {}
8129
+ financeState.traderSid = null;
8130
+ }
8131
+ const s = engine._load(engine.createSession().id);
8132
+ s.messages = s.messages || [];
8133
+ s.bgTitle = 'Beast Finance · Trader'; /* _view.isBg → sohbet geçmişinde gizli */
8134
+ try {
8135
+ fs.appendFileSync(
8136
+ engine._file(s.id),
8137
+ JSON.stringify({ t: 'meta2', bgOf: '', title: 'Beast Finance · Trader', at: new Date().toISOString() }) + '\n'
8138
+ );
8139
+ } catch {}
8140
+ engine.cache.set(s.id, s);
8141
+ engine.markFinance(s.id, true); /* kalıcı finance etiketi (trader rolüyle) */
8142
+ s.workspace = financeDir(); /* trader kendi klasöründe çalışır */
8143
+ financeState.traderSid = s.id;
8144
+ return s;
8145
+ }
8146
+
8147
+ function finApplyTraderFields(s) {
8148
+ const f = finCfg();
8149
+ s.finance = true;
8150
+ s.financeTrader = true;
8151
+ s.financeAuto = f.allowTrading === true;
8152
+ s.financeSymbols = f.symbols;
8153
+ s.financeStrategy = String(f.strategy || '');
8154
+ s.financeLimits = { maxLot: f.maxLot, maxPositions: f.maxPositions };
8155
+ engine.cache.set(String(s.id), s);
8156
+ }
8157
+
8158
+ function finTraderBrief() {
8159
+ const f = finCfg();
8160
+ return [
8161
+ 'Beast Finance TRADER başlatıldı — ilk tur: strateji çerçeveni kur ve piyasa taramasını yap.',
8162
+ `İzleme listesi: ${(f.symbols || []).join(', ')}`,
8163
+ `Tur aralığı: ${f.intervalSec} sn · Max lot: ${f.maxLot} · Max eşzamanlı pozisyon: ${f.maxPositions}`,
8164
+ f.allowTrading
8165
+ ? 'Otomatik işlem AÇIK: mt5_trade/mt5_close/mt5_modify/mt5_pending kullanabilirsin (limitler sistemce zorlanır).'
8166
+ : 'Otomatik işlem KAPALI: SADECE analiz + net işlem önerileri yaz (sembol, yön, giriş, SL, TP, sebep); işlem açma.',
8167
+ f.strategy ? `Sahibinin strateji notu: ${f.strategy}` : 'Strateji notu yok: trend + destek/direnç + momentum ile temel okuma yap.',
8168
+ 'Bu turda: mt5_status → hesap/pozisyon/fiyat verisi → değerlendirme → kararlar (veya BEKLE: sebep) → kısa rapor.',
8169
+ ].join('\n');
8170
+ }
8171
+
8172
+ function finTraderRound() {
8173
+ if (!financeState.traderOn) return;
8174
+ if (!engine) return;
8175
+ const s = finTraderSession();
8176
+ finApplyTraderFields(s);
8177
+ if (engine.isBusy(s.id)) {
8178
+ /* hâlâ çalışıyor — done eventinde tekrar planlanır */
8179
+ return;
8180
+ }
8181
+ financeState.traderRounds += 1;
8182
+ const f = finCfg();
8183
+ const auto = f.allowTrading
8184
+ ? 'İşlem açabilirsin — limitlere uy, SL\u2019siz pozisyon bırakma.'
8185
+ : 'Otomatik işlem KAPALI — sadece analiz + öneri.';
8186
+ const ok = engine.send(
8187
+ s.id,
8188
+ `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.`,
8189
+ { userAction: false }
8190
+ );
8191
+ if (ok) {
8192
+ financeState.lastRoundAt = Date.now();
8193
+ finPush('trader', { state: 'running', round: financeState.traderRounds });
8194
+ } else {
8195
+ /* stop kapısı/yoğunluk — yarım dakika sonra sessizce tekrar dene */
8196
+ clearTimeout(financeState.traderTimer);
8197
+ financeState.traderTimer = setTimeout(() => { try { finTraderRound(); } catch {} }, 30000);
8198
+ }
8199
+ }
8200
+
8201
+ function finFlushOnDone(ev) {
8202
+ if (!ev || (ev.type !== 'done' && ev.type !== 'error')) return;
8203
+ if (String(ev.sessionId || '') !== String(financeState.traderSid || '')) return;
8204
+ if (!financeState.traderOn) return;
8205
+ clearTimeout(financeState.traderTimer);
8206
+ const f = finCfg();
8207
+ const iv = Math.max(30, Number(f.intervalSec) || 120) * 1000;
8208
+ financeState.lastRoundAt = Date.now();
8209
+ finPush('trader', { state: 'idle', round: financeState.traderRounds, nextInSec: iv / 1000 });
8210
+ financeState.traderTimer = setTimeout(() => { try { finTraderRound(); } catch {} }, iv);
8211
+ }
8212
+
8213
+ ipcMain.handle('finance:state', async () => {
8214
+ const f = finCfg();
8215
+ const busy = financeState.traderSid && engine ? engine.isBusy(financeState.traderSid) : false;
8216
+ return {
8217
+ ok: true,
8218
+ mode: financeState.mode,
8219
+ cfg: f,
8220
+ bridge: mt5bridge.status(),
8221
+ trader: { on: financeState.traderOn, busy, rounds: financeState.traderRounds, lastAt: financeState.lastRoundAt, sid: financeState.traderSid },
8222
+ models: engine ? engine.publicState().models : [],
8223
+ };
8224
+ });
8225
+
8226
+ ipcMain.handle('finance:snapshot', async () => {
8227
+ const f = finCfg();
8228
+ const st = financeEnsureBridge();
8229
+ let account = st.account || null;
8230
+ let positions = [];
8231
+ let symbols = [];
8232
+ if (st.running) {
8233
+ const [a, p, sy] = await Promise.all([
8234
+ mt5bridge.call('account', {}, 8000).catch(() => null),
8235
+ mt5bridge.call('positions', {}, 8000).catch(() => null),
8236
+ mt5bridge.call('symbols', { symbols: f.symbols || [] }, 10000).catch(() => null),
8237
+ ]);
8238
+ if (a && a.ok) account = a.data && a.data.account;
8239
+ if (p && p.ok) positions = (p.data && p.data.positions) || [];
8240
+ if (sy && sy.ok) symbols = (sy.data && sy.data.symbols) || [];
8241
+ }
8242
+ const busy = financeState.traderSid && engine ? engine.isBusy(financeState.traderSid) : false;
8243
+ return {
8244
+ ok: true,
8245
+ bridge: { running: st.running, connected: st.connected, error: st.error, terminal: st.terminal, python: st.python },
8246
+ account,
8247
+ positions,
8248
+ symbols,
8249
+ cfg: f,
8250
+ trader: { on: financeState.traderOn, busy, rounds: financeState.traderRounds, lastAt: financeState.lastRoundAt },
8251
+ };
8252
+ });
8253
+
8254
+ ipcMain.handle('finance:mode', async (_e, payload) => {
8255
+ financeState.mode = !!(payload && payload.on);
8256
+ const sid = String((payload && payload.sessionId) || '');
8257
+ if (sid && engine) {
8258
+ try {
8259
+ let s = engine.cache.get(sid);
8260
+ if (!s) { try { s = engine._load(sid); } catch {} }
8261
+ if (s && !s.botId && !s.bgJob) {
8262
+ if (financeState.mode) {
8263
+ engine.markFinance(sid, false); /* kalıcı etiket: finance geçmişine girer */
8264
+ finApplyTraderFields(s);
8265
+ s.financeTrader = false; /* chat copilot'ı — trader değil */
8266
+ } else {
8267
+ engine.unmarkFinance(sid); /* normal sohbete döner */
8268
+ }
8269
+ engine.cache.set(sid, s);
8270
+ }
8271
+ } catch {}
8272
+ }
8273
+ return { ok: true, mode: financeState.mode };
8274
+ });
8275
+
8276
+ /* MT5 sembol seçici: terminaldeki tüm semboller (isteğe bağlı *filter*) */
8277
+ ipcMain.handle('finance:symbols:list', async (_e, filter) => {
8278
+ if (!mt5bridge.running) return { ok: false, error: 'MT5 köprüsü bağlı değil — terminal açık mı?' };
8279
+ return await mt5bridge.call('all_symbols', { filter: String(filter || '').trim() }, 20000);
8280
+ });
8281
+
8282
+ ipcMain.handle('finance:settings', async (_e, patch) => {
8283
+ const f = finCfg();
8284
+ const p = patch || {};
8285
+ if (p.symbols !== undefined) {
8286
+ const arr = Array.isArray(p.symbols) ? p.symbols : String(p.symbols).split(/[,\s]+/);
8287
+ f.symbols = arr.map((s) => String(s).trim().toUpperCase()).filter(Boolean).slice(0, 20);
8288
+ }
8289
+ if (p.intervalSec !== undefined) f.intervalSec = Math.max(30, Math.min(3600, Math.round(Number(p.intervalSec) || 120)));
8290
+ if (p.maxLot !== undefined) f.maxLot = Math.max(0.01, Math.min(100, Number(p.maxLot) || 0.1));
8291
+ if (p.maxPositions !== undefined) f.maxPositions = Math.max(1, Math.min(20, Math.round(Number(p.maxPositions) || 3)));
8292
+ if (p.allowTrading !== undefined) f.allowTrading = !!p.allowTrading;
8293
+ if (p.strategy !== undefined) f.strategy = String(p.strategy || '').slice(0, 2000);
8294
+ if (p.pythonPath !== undefined) f.pythonPath = String(p.pythonPath || '').trim();
8295
+ if (p.terminalPath !== undefined) f.terminalPath = String(p.terminalPath || '').trim();
8296
+ if (p.traderSel !== undefined) {
8297
+ f.traderSel = String(p.traderSel || '').trim();
8298
+ if (financeState.traderSid && engine) {
8299
+ try { engine.setSessionModel(financeState.traderSid, f.traderSel || null); } catch {}
8300
+ }
8301
+ }
8302
+ saveSettings();
8303
+ /* trader oturumu alanlarını tazele (oturum yoksa oluşturma — start'ta kurulur) */
8304
+ if (financeState.traderSid && engine) {
8305
+ try {
8306
+ const ts = engine.cache.get(financeState.traderSid);
8307
+ if (ts) finApplyTraderFields(ts);
8308
+ } catch {}
8309
+ }
8310
+ return { ok: true, cfg: f };
8311
+ });
8312
+
8313
+ ipcMain.handle('finance:trader:start', async () => {
8314
+ if (!engine) return { ok: false, error: 'ajan hazır değil' };
8315
+ const f = finCfg();
8316
+ if (!engine.publicState().hasModel && !f.traderSel) return { ok: false, error: 'model yok — Ayarlar → Provider' };
8317
+ const s = finTraderSession();
8318
+ finApplyTraderFields(s);
8319
+ try { engine.setSessionModel(s.id, f.traderSel || null); } catch {}
8320
+ financeState.traderOn = true;
8321
+ financeState.traderRounds = 0;
8322
+ try { engine.clearStop(); } catch {}
8323
+ const ok = engine.send(s.id, finTraderBrief(), { userAction: true });
8324
+ if (!ok) {
8325
+ financeState.traderOn = false;
8326
+ return { ok: false, error: 'trader oturumu meşgul — birkaç saniye sonra tekrar dene' };
8327
+ }
8328
+ financeState.lastRoundAt = Date.now();
8329
+ financeLog('[trader] başlatıldı (model: ' + (f.traderSel || 'genel aktif model') + ')');
8330
+ finPush('trader', { state: 'running', round: 0 });
8331
+ return { ok: true, sid: s.id };
8332
+ });
8333
+
8334
+ ipcMain.handle('finance:trader:stop', async () => {
8335
+ financeState.traderOn = false;
8336
+ clearTimeout(financeState.traderTimer);
8337
+ financeState.traderTimer = null;
8338
+ let interrupted = false;
8339
+ if (financeState.traderSid && engine && engine.isBusy(financeState.traderSid)) {
8340
+ try { interrupted = engine.interrupt(financeState.traderSid, 'kullanıcı Beast Finance trader ajanını durdurdu'); } catch {}
8341
+ }
8342
+ financeLog('[trader] durduruldu');
8343
+ finPush('trader', { state: 'stopped' });
8344
+ return { ok: true, interrupted };
8345
+ });
8346
+
8347
+ ipcMain.handle('finance:connect', async () => {
8348
+ mt5bridge.stop();
8349
+ const st = financeEnsureBridge();
8350
+ return { ok: true, bridge: st };
8351
+ });
8352
+
8353
+ ipcMain.handle('finance:close', async (_e, payload) => {
8354
+ const ticket = Number(payload && payload.ticket);
8355
+ if (!ticket) return { ok: false, error: 'ticket gerekli' };
8356
+ if (!mt5bridge.running) return { ok: false, error: 'MT5 köprüsü bağlı değil' };
8357
+ const r = await mt5bridge.call('close', { ticket, volume: Number(payload && payload.volume) || 0 }, 20000);
8358
+ if (r && r.ok) {
8359
+ financeLog('[panel] pozisyon kapatıldı: ticket ' + ticket);
8360
+ finPush('trade', { line: 'POZİSYON KAPANDI (panel): ticket ' + ticket });
8361
+ }
8362
+ return r;
8363
+ });
8364
+
8365
+ ipcMain.handle('finance:install', async () => {
8366
+ financeState.installTried = false;
8367
+ financeTryInstall();
8368
+ return { ok: true, installing: true };
8369
+ });
8370
+
8371
+
7990
8372
  /* düşünme (reasoning) seviyesi */
7991
8373
  ipcMain.handle('think:set', (_e, v) => {
7992
8374
  setThinkLevel(v);