beast-agent 2.13.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
@@ -3106,10 +3106,18 @@ function reloadBackend() {
3106
3106
  bcFlushOnDone(ev); /* Beast Code kuyruğunu iş bitiminde boşalt */
3107
3107
  stFlushOnDone(ev); /* Beast Studio kuyruğunu iş bitiminde boşalt */
3108
3108
  sbFlushOnDone(ev); /* Sandbox kuyruğunu iş bitiminde boşalt */
3109
+ finFlushOnDone(ev); /* Finance trader döngüsü: iş bitince sıradaki turu planla */
3109
3110
  /* BC canlı önizleme: ajan bir dev server başlattıysa adresi yakala —
3110
3111
  preview butonu ve otomatik açılış DAİMA bu sunucuyu öncelikli kullanır */
3111
3112
  if (ev.type === 'bc-preview' && /^https?:\/\/(?:localhost|127\.0\.0\.1)(?::\d{2,5})?/i.test(String(ev.url || ''))) {
3113
+ /* klasör değiştiyse eski sunucu bayat: KAPAT, en yeniye geç */
3114
+ try {
3115
+ if (bcLastServerUrl && bcLastServerRoot && path.relative(bcLastServerRoot, ideRoot()) !== '') {
3116
+ bcKillServer(bcLastServerUrl);
3117
+ }
3118
+ } catch {}
3112
3119
  bcLastServerUrl = String(ev.url);
3120
+ try { bcLastServerRoot = ideRoot(); } catch { bcLastServerRoot = ''; }
3113
3121
  }
3114
3122
  /* Expo Go QR: ajanın dev sunucusu exp:// adresi yazdıysa QR üretip panele bas */
3115
3123
  if (ev.type === 'bc-expurl') {
@@ -4896,6 +4904,13 @@ ipcMain.handle('sessions:create', () => {
4896
4904
  engine.setSessionTools(v.id, null);
4897
4905
  }
4898
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
+ }
4899
4914
  return v;
4900
4915
  });
4901
4916
  ipcMain.handle('sessions:open', (_e, id) => engine.openSession(id));
@@ -4924,6 +4939,25 @@ ipcMain.handle('agent:send', (_e, { sessionId, text }) => {
4924
4939
  }
4925
4940
  }
4926
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 {}
4927
4961
  if (t === '/stop' || t === '/start') {
4928
4962
  handleGlobalStopStart(sessionId, t);
4929
4963
  return true;
@@ -6318,6 +6352,8 @@ ipcMain.handle('headroom:toggle', async (_e, on) => {
6318
6352
  ipcMain.handle('headroom:status', () => headroom.probe());
6319
6353
  ipcMain.handle('headroom:stats', () => headroom.stats());
6320
6354
 
6355
+ /* ANDROID EMÜLATÖR: kaldırıldı — mobil önizleme tunnel (telefon QR) yoluyla yapılır */
6356
+
6321
6357
  /* embedding modelini şimdi indir (mem0 arama yolu ısıtılır) */
6322
6358
  ipcMain.handle('embed:prefetch', () => {
6323
6359
  try {
@@ -7951,6 +7987,388 @@ function studioWatchStop() {
7951
7987
  if (studioWatchTimer) { clearTimeout(studioWatchTimer); studioWatchTimer = null; }
7952
7988
  }
7953
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
+
7954
8372
  /* düşünme (reasoning) seviyesi */
7955
8373
  ipcMain.handle('think:set', (_e, v) => {
7956
8374
  setThinkLevel(v);
@@ -8127,6 +8545,14 @@ ipcMain.handle('ide:setroot', async () => {
8127
8545
  properties: ['openDirectory'],
8128
8546
  });
8129
8547
  if (r.canceled || !r.filePaths || !r.filePaths[0]) return { ok: false, canceled: true };
8548
+ /* klasör değişti: eski klasörün dev server'ı bayat — kill + unut */
8549
+ try {
8550
+ if (bcLastServerUrl) {
8551
+ bcKillServer(bcLastServerUrl);
8552
+ bcLastServerUrl = '';
8553
+ bcLastServerRoot = '';
8554
+ }
8555
+ } catch {}
8130
8556
  settings.ideRoot = r.filePaths[0];
8131
8557
  saveSettings();
8132
8558
  ideWatchStart(); // yeni kökte izleme yeniden kurulur
@@ -8750,6 +9176,39 @@ ipcMain.handle('sandbox:openurl', async (_e, url) => {
8750
9176
  ve "clean URL" yolları file://'da çalışmaz. Her preview http://127.0.0.1 üzerinden
8751
9177
  servis edilir; ajan kendi dev sunucusunu başlattıysa o adres önceliklidir. */
8752
9178
  let bcLastServerUrl = ''; /* ajanın başlattığı dev server adresi (bc-preview'dan yakalanır) */
9179
+ let bcLastServerRoot = ''; /* adresin yakalandığı workspace kökü — klasör değişince bayat sayılır */
9180
+
9181
+ /* dev server'ı port'tan bulup süreç AĞACINI kapat (best-effort).
9182
+ Ajan arka planda başlattığı sunucular izlenmez — port üzerinden bulunur. */
9183
+ function bcKillServer(url) {
9184
+ try {
9185
+ const m = /:(\d{2,5})(?:\/|$)/.exec(String(url || ''));
9186
+ if (!m) return false;
9187
+ const port = m[1];
9188
+ const { execSync } = require('child_process');
9189
+ const out = String(execSync(
9190
+ 'netstat -ano | findstr ":' + port + '" | findstr LISTENING',
9191
+ { timeout: 6000, stdio: 'pipe', encoding: 'utf8', windowsHide: true }
9192
+ ));
9193
+ const pids = new Set();
9194
+ for (const line of out.split(/\r?\n/)) {
9195
+ const p = /\s(\d+)\s*$/.exec(String(line).trim());
9196
+ if (p) pids.add(p[1]);
9197
+ }
9198
+ let killed = 0;
9199
+ for (const pid of pids) {
9200
+ if (Number(pid) === process.pid) continue; /* kendimize dokunma */
9201
+ try {
9202
+ spawn('taskkill', ['/pid', String(pid), '/T', '/F'], { windowsHide: true });
9203
+ killed++;
9204
+ } catch {}
9205
+ }
9206
+ try { log.info('bc', 'bayat dev server kapatıldı (port ' + port + ', ' + killed + ' süreç)'); } catch {}
9207
+ return killed > 0;
9208
+ } catch {
9209
+ return false; /* port boş / netstat hata — yok say */
9210
+ }
9211
+ }
8753
9212
  const bcStatic = { server: null, root: '', base: '' };
8754
9213
  /* MOBİL PROJE önizleme: npm run web (expo start --web) süreci + canlı adres */
8755
9214
  const bcMobile = { proc: null, url: '', root: '' };
@@ -9026,6 +9485,13 @@ ipcMain.handle('ide:preview', async () => {
9026
9485
  if (!entry) return { ok: false, error: 'workspace kökünde index.html yok — önce agent\'a siteyi yazdır' };
9027
9486
  /* forceVisible: preview'a basınca tarayıcı ikonuna basmaya gerek kalmasın */
9028
9487
  setBrowserOpen(true, true);
9488
+ /* KLASÖR UYUŞMAZLIĞI: kayıtlı dev server ESKİ klasörün artıklarıysa
9489
+ kill et ve unut — yeni klasör kendi sunucusuyla başlasın */
9490
+ if (bcLastServerUrl && bcLastServerRoot && path.relative(bcLastServerRoot, root) !== '') {
9491
+ bcKillServer(bcLastServerUrl);
9492
+ bcLastServerUrl = '';
9493
+ bcLastServerRoot = '';
9494
+ }
9029
9495
  let url = '';
9030
9496
  if (bcLastServerUrl) {
9031
9497
  url = bcLastServerUrl; /* ajanın kendi dev sunucusu öncelikli */