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/package.json +1 -1
- package/src/agent/engine.js +154 -3
- package/src/agent/financetools.js +307 -0
- package/src/agent/llm.js +3 -1
- package/src/agent/scripts/mt5_bridge.py +381 -0
- package/src/agent/tools.js +10 -3
- package/src/main.js +466 -0
- package/src/mt5bridge.js +215 -0
- package/src/preload.js +11 -0
- package/src/renderer/i18n.js +3 -1
- package/src/renderer/index.html +76 -1
- package/src/renderer/renderer.js +562 -4
- package/src/renderer/style.css +393 -1
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 */
|