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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "beast-agent",
3
3
  "productName": "Beast Agent",
4
- "version": "2.13.0",
4
+ "version": "2.15.0",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
@@ -10,6 +10,9 @@ const llm = require('./llm');
10
10
  const { chatStream, chatStreamAuto, chatOnce } = llm;
11
11
  const { chatCompletionsUrl, beastDir } = require('./config');
12
12
  const tools = require('./tools');
13
+ let financetools = null;
14
+ try { financetools = require('./financetools'); } catch {}
15
+ const FINANCE_TOOL_SET = new Set(financetools ? financetools.NAMES : []);
13
16
  const research = require('./research');
14
17
  const agentdefs = require('./agentdefs');
15
18
  const memory = require('./memory');
@@ -1064,6 +1067,39 @@ class Engine {
1064
1067
  return removed;
1065
1068
  }
1066
1069
 
1070
+ /* Düşünme geri-besleme kurtarması: asistan mesajlarındaki reasoning_content'i
1071
+ bellekten + jsonl'den temizler. "must be passed back" hatasını veren sağlayıcı
1072
+ için tek kurtarma yolu — döndürür: temizlenen mesaj sayısı */
1073
+ _stripAssistantReasoning(session) {
1074
+ let dropped = 0;
1075
+ for (const m of session.messages) {
1076
+ if (m && m.role === 'assistant' && (m.reasoning_content || m.reasoning)) {
1077
+ delete m.reasoning_content;
1078
+ delete m.reasoning;
1079
+ dropped++;
1080
+ }
1081
+ }
1082
+ if (!dropped) return 0;
1083
+ try {
1084
+ const file = this._file(session.id);
1085
+ const lines = fs.readFileSync(file, 'utf8').split('\n');
1086
+ for (let i = 0; i < lines.length; i++) {
1087
+ if (!lines[i].trim()) continue;
1088
+ let r;
1089
+ try { r = JSON.parse(lines[i]); } catch { continue; }
1090
+ if (r.t === 'msg' && r.role === 'assistant' && (r.reasoning_content || r.reasoning)) {
1091
+ delete r.reasoning_content;
1092
+ delete r.reasoning;
1093
+ lines[i] = JSON.stringify(r);
1094
+ }
1095
+ }
1096
+ const tmp = file + '.tmp';
1097
+ fs.writeFileSync(tmp, lines.join('\n'));
1098
+ fs.renameSync(tmp, file);
1099
+ } catch {}
1100
+ return dropped;
1101
+ }
1102
+
1067
1103
  _load(id) {
1068
1104
  if (this.cache.has(id)) return this.cache.get(id);
1069
1105
  const file = this._file(id);
@@ -1104,6 +1140,13 @@ class Engine {
1104
1140
  } else if (rec.t === 'bcws') {
1105
1141
  /* Beast Code oturumu: hangi çalışma klasörüne bağlı */
1106
1142
  session.bcWs = String(rec.ws || '');
1143
+ } else if (rec.t === 'fin') {
1144
+ /* Beast Finance oturumu: kalıcı izolasyon etiketi (finoff kapatır) */
1145
+ session.finance = true;
1146
+ session.financeTrader = !!rec.trader;
1147
+ } else if (rec.t === 'finoff') {
1148
+ session.finance = false;
1149
+ session.financeTrader = false;
1107
1150
  } else if (rec.t === 'msg') {
1108
1151
  delete rec.t;
1109
1152
  session.messages.push(rec);
@@ -1240,10 +1283,36 @@ class Engine {
1240
1283
  count: s.messages.length,
1241
1284
  isBg,
1242
1285
  isBotDm: !!s.isBotDm,
1286
+ finance: !!s.finance,
1243
1287
  bgStatus: isBg ? (job ? job.status : null) : null,
1244
1288
  };
1245
1289
  }
1246
1290
 
1291
+ /* Beast Finance etiketi: oturumu finance sohbeti olarak KALICI işaretler
1292
+ (jsonl'e 'fin' satırı düşer — restart sonrası da hatırlanır) */
1293
+ markFinance(id, trader = false) {
1294
+ const s = this._load(String(id));
1295
+ s.finance = true;
1296
+ s.financeTrader = !!trader;
1297
+ try {
1298
+ fs.appendFileSync(this._file(s.id), JSON.stringify({ t: 'fin', trader: !!trader, at: nowIso() }) + '\n');
1299
+ } catch {}
1300
+ this.cache.set(String(s.id), s);
1301
+ return s;
1302
+ }
1303
+
1304
+ /* Finance etiketini kaldır — oturum normal sohbete döner */
1305
+ unmarkFinance(id) {
1306
+ const s = this._load(String(id));
1307
+ s.finance = false;
1308
+ s.financeTrader = false;
1309
+ try {
1310
+ fs.appendFileSync(this._file(s.id), JSON.stringify({ t: 'finoff', at: nowIso() }) + '\n');
1311
+ } catch {}
1312
+ this.cache.set(String(s.id), s);
1313
+ return s;
1314
+ }
1315
+
1247
1316
  /* #17 paralel ajan oturumları sol sohbet listesine KARIŞMAZ;
1248
1317
  geçmişleri sağdaki Paralel Ajanlar panelinde izlenir */
1249
1318
  listSessions() {
@@ -1566,6 +1635,46 @@ class Engine {
1566
1635
  );
1567
1636
  }
1568
1637
 
1638
+ /* Beast Finance: MT5 trading ajanı — chat copilot'ı VEYA otonom trader.
1639
+ Oturum alanları (main enjekte eder): financeTrader, financeAuto,
1640
+ financeSymbols, financeStrategy, financeLimits */
1641
+ buildFinanceSystem(session) {
1642
+ const nowD = new Date();
1643
+ const localDate = nowD.toLocaleDateString('tr-TR');
1644
+ const localTime = String(nowD.getHours()).padStart(2, '0') + ':00';
1645
+ const isTrader = !!(session && session.financeTrader);
1646
+ const auto = !!(session && session.financeAuto);
1647
+ const symbols = Array.isArray(session && session.financeSymbols) ? session.financeSymbols.join(', ') : String((session && session.financeSymbols) || '');
1648
+ const lim = (session && session.financeLimits) || {};
1649
+ const modeBlock = isTrader
1650
+ ? auto
1651
+ ? 'ROL: OTONOM TRADER ⚡ — tur tur çalışıyorsun. Her turda: veri çek → değerlendir → GEREKİRSE işlem aç/kapat/SL-TP güncelle → kısa rapor. Sistem seni turlar; sohbette konuşmazsın.'
1652
+ : 'ROL: GÖZLEMCİ ANALİST 🔍 — tur tur piyasa tararsın ama OTOMATİK İŞLEM KAPALI: mt5_trade/mt5_pending KULLANMA. Fırsatları net öneri olarak yaz (sembol, yön, giriş, SL, TP, sebep); sahibi onaylarsa açılır.'
1653
+ : 'ROL: TRADING CO-PİLOT 💬 — sahibinle sohbet ediyorsun. Veri isterse mt5_* araçlarıyla çek, analiz et; işlem istenirse limitler içinde uygula, istenmezse sadece öneri ver.';
1654
+ const proj = this._projectInstructions(session);
1655
+ return (
1656
+ 'Sen BEAST FİNANS\u2019sın — bilgisayardaki MetaTrader 5 (MT5) terminaline köprüyle bağlı trading ajanı.\n' +
1657
+ 'ORTAM: Beast Finance uygulamasındasın — solda sohbet geçmişi + izleyiciler, ortada bu sohbet, SAĞ panelde MT5 işlem panosu (hesap, pozisyonlar, piyasa, AKIŞ) var.\n' +
1658
+ 'KÖPRÜ: MT5 terminaline Python stdio köprüsüyle bağlısın; panel 3 saniyede bir hesap/pozisyon/piyasa verisini tazeler. mt5_* araç çağrıların ve işlem hareketlerin (açılan/kapanan pozisyonlar) SAĞ paneldeki AKIŞ akışına canlı düşer — panelin orayı izlediğini bil.\n' +
1659
+ 'GÖREV ALANI: piyasa verisi okuma (fiyat/hesap/pozisyon/geçmiş), teknik değerlendirme, pozisyon yönetimi (aç/kapat/SL/TP), risk disiplini ve kısa karar raporları. Kod yazma işi DEĞİLDİR.\n' +
1660
+ modeBlock + '\n' +
1661
+ 'MT5 ARAÇLARI: mt5_status (bağlantı), mt5_account (hesap), mt5_market (canlı fiyat), mt5_positions (açık pozisyonlar), mt5_orders (bekleyen emirler), mt5_history (kapanan işlemler), mt5_trade (piyasa emri), mt5_close (kapat), mt5_modify (SL/TP), mt5_pending (bekleyen emir), mt5_cancel (emir iptal).\n' +
1662
+ 'VERİ AKIŞI (her değerlendirmede): mt5_account + mt5_positions + mt5_market çağrılarını AYNI turda PARALEL ver; gerekiyorsa mt5_history ile son işlemleri gör.\n' +
1663
+ (symbols ? `İZLEME LİSTESİ: ${symbols}\n` : '') +
1664
+ (lim.maxLot ? `LİMİTLER: max lot ${lim.maxLot}` + (lim.maxPositions ? ` · max eşzamanlı pozisyon ${lim.maxPositions}` : '') + ' — bunları aşıp araç kullanma; sistem zaten reddeder.\n' : '') +
1665
+ 'RİSK DİSİPLİNİ:\n' +
1666
+ '- SL\u2019siz pozisyon BIRAKMA: açtığın her işlemde mantıklı bir SL koy; SL\u2019siz kalan pozisyonu mt5_modify ile tamamla.\n' +
1667
+ '- Martingale/kademeli lot artışı YASAK; kaybı geri kovalama (revenge trade) YASAK.\n' +
1668
+ '- Emin olmadığında İŞLEM YOK — "BEKLE: <sebep>" yaz. Sık işlem > iyi işlem.\n' +
1669
+ '- Hesap kaldıracı ve serbest marja göre pozisyon boyutunu düşük tut; tek işlemde serbest marjın büyük kısmını riske atma.\n' +
1670
+ (session && session.financeStrategy ? `SAHİBİNİN STRATEJİ NOTU (önceliklidir):\n${session.financeStrategy}\n` : '') +
1671
+ 'RAPOR DİSİPLİNİ: kısa ve sayısal — sembol, yön, lot, giriş, SL/TP, sebep tek satırda. Uzun fal açma; tablo/liste kullanabilirsin.\n' +
1672
+ `Yerel zaman: ${localDate} ${localTime} — piyasa saatlerine dikkat et (borsa/metal seansları kapalıysa spread genişler; hafta sonu FX kapalıdır).\n` +
1673
+ (proj ? '# PROJE TALİMATLARI\n' + proj + '\n' : '') +
1674
+ FORMAT_RULES
1675
+ );
1676
+ }
1677
+
1569
1678
  async buildSystem(queryText, session) {
1570
1679
  /* BOT OTURUMU: non-admin bota bağlıysa global Beast hafızası HIÇ girmez —
1571
1680
  o botun kendi SOUL/USER/MEMORY dosyaları kullanılır (tam izolasyon) */
@@ -3137,9 +3246,11 @@ class Engine {
3137
3246
  ? this.buildBgSystem(session)
3138
3247
  : session.studio
3139
3248
  ? this.buildStudioSystem(session)
3140
- : session.bcCode
3141
- ? this.buildBcSystem(session)
3142
- : await this.buildSystem(promptText, session);
3249
+ : session.finance
3250
+ ? this.buildFinanceSystem(session)
3251
+ : session.bcCode
3252
+ ? this.buildBcSystem(session)
3253
+ : await this.buildSystem(promptText, session);
3143
3254
  // Granül izin: oturumun yetki seviyesine göre araç seti daraltılır.
3144
3255
  // Çoklu izin (ör. web+read) seçiliyse kümeler BİRLEŞİR — hepsinin araçları açık olur.
3145
3256
  const perms = this.sessionPermFor(session.id);
@@ -3149,6 +3260,10 @@ class Engine {
3149
3260
  for (const p of perms) for (const t of PERM_TOOL_SETS[p] || []) allowedSet.add(t);
3150
3261
  }
3151
3262
  let activeTools = allowedSet ? toolsList.filter((t) => allowedSet.has(t.function.name)) : toolsList;
3263
+ /* Beast Finance: mt5_* araçları YALNIZ finance oturumlarında görünür */
3264
+ if (FINANCE_TOOL_SET.size && !(session && session.finance)) {
3265
+ activeTools = activeTools.filter((t) => !FINANCE_TOOL_SET.has(t.function.name));
3266
+ }
3152
3267
  /* bot skill kısıtı: bota verilen yetkiye göre araç seti daraltılır */
3153
3268
  const toolLimit = this.sessionTools.get(String(session.id));
3154
3269
  if (toolLimit) activeTools = activeTools.filter((t) => toolLimit.has(t.function.name));
@@ -3240,6 +3355,37 @@ class Engine {
3240
3355
  arındırıp turu BİR KEZ görüntüsüz dener. */
3241
3356
  const msgStr = String((e && e.message) || '');
3242
3357
  const aborted = signal && signal.aborted;
3358
+ /* DÜŞÜNME GERİ-BESLEME KURTARMASI: "reasoning_content ... must be passed
3359
+ back" hatası eski oturumlarda (reasoning saklanmadan önce) olur —
3360
+ geçmişi reasoning'siz arındırıp turu BİR KEZ yeniden dene */
3361
+ if (!aborted && /reasoning_content/i.test(msgStr)) {
3362
+ const dropped = this._stripAssistantReasoning(session);
3363
+ if (dropped) {
3364
+ emitSafe(this, session.id, {
3365
+ type: 'status',
3366
+ status: `🧠 düşük uyumlu sağlayıcı: geçmişteki düşünme içerikleri temizlendi (${dropped} mesaj) — tur yeniden deneniyor`,
3367
+ });
3368
+ payload = this._buildPayload(system, session.messages, session.notes, dynBudget, session.summary);
3369
+ res = await this._streamWithFallbacks(
3370
+ session,
3371
+ payload,
3372
+ activeTools,
3373
+ signal,
3374
+ onDelta,
3375
+ sel,
3376
+ role === 'vision'
3377
+ );
3378
+ const actualR = res.usage && res.usage.prompt_tokens;
3379
+ if (actualR > 0) {
3380
+ const predicted = this._payloadTokens(payload);
3381
+ if (predicted > 50) {
3382
+ const r = clamp(actualR / predicted, 0.75, 3);
3383
+ this.tokRatio = clamp(this.tokRatio * 0.7 + r * 0.3, 0.75, 3);
3384
+ }
3385
+ }
3386
+ return res;
3387
+ }
3388
+ }
3243
3389
  const imgish =
3244
3390
  !aborted &&
3245
3391
  /image|vision|multimodal|no endpoints/i.test(msgStr);
@@ -3395,6 +3541,10 @@ class Engine {
3395
3541
 
3396
3542
  const assistant = { role: 'assistant', content: res.content || '' };
3397
3543
  if (res.toolCalls && res.toolCalls.length) assistant.tool_calls = res.toolCalls;
3544
+ /* DÜŞÜNME MODU GERİ-BESLEMESİ: Console Go gibi sağlayıcılar thinking
3545
+ modunda reasoning_content'in GEÇMİŞTE geri gönderilmesini şart koşar
3546
+ ("must be passed back to the API") — saklamazsak sonraki tur 400'le ölür */
3547
+ if (res.reasoning) assistant.reasoning_content = res.reasoning;
3398
3548
  session.messages.push(assistant);
3399
3549
  try {
3400
3550
  this._append(session, assistant);
@@ -4078,6 +4228,7 @@ const skills = require('./skills');
4078
4228
  );
4079
4229
  const assistant = { role: 'assistant', content: res.content || '' };
4080
4230
  if (res.toolCalls && res.toolCalls.length) assistant.tool_calls = res.toolCalls;
4231
+ if (res.reasoning) assistant.reasoning_content = res.reasoning; /* thinking geri-besleme */
4081
4232
  msgs.push(assistant);
4082
4233
  if (!res.toolCalls || !res.toolCalls.length) return res.content || '(alt-agent boş döndü)';
4083
4234
  for (const tc of res.toolCalls) {
@@ -0,0 +1,307 @@
1
+ 'use strict';
2
+
3
+ /* Beast Finance — MT5 ajan araçları.
4
+ Handler'lar main süreçteki mt5bridge singleton'ına bağlanır; işlem açan
5
+ araçlar settings.finance.allowTrading / maxLot / maxPositions ile sınırlanır.
6
+ main tarafı setConfig/setNotify ile ayar ve bildirim kancasını enjekte eder. */
7
+
8
+ const mt5 = require('../mt5bridge');
9
+
10
+ const NAMES = [
11
+ 'mt5_status',
12
+ 'mt5_account',
13
+ 'mt5_market',
14
+ 'mt5_positions',
15
+ 'mt5_orders',
16
+ 'mt5_history',
17
+ 'mt5_trade',
18
+ 'mt5_close',
19
+ 'mt5_modify',
20
+ 'mt5_pending',
21
+ 'mt5_cancel',
22
+ ];
23
+
24
+ let getCfg = () => ({ allowTrading: false, maxLot: 0.1, maxPositions: 3, symbols: [] });
25
+ let notify = () => {};
26
+
27
+ function setConfig(fn) {
28
+ if (typeof fn === 'function') getCfg = fn;
29
+ }
30
+
31
+ function setNotify(fn) {
32
+ if (typeof fn === 'function') notify = fn;
33
+ }
34
+
35
+ function clampLot(v, maxLot) {
36
+ const n = Number(v);
37
+ if (!isFinite(n) || n <= 0) return 0;
38
+ return Math.min(n, maxLot);
39
+ }
40
+
41
+ async function bcall(method, params, timeoutMs) {
42
+ const r = await mt5.call(method, params, timeoutMs);
43
+ if (!r || r.ok !== true) throw new Error((r && r.error) || 'MT5 hatası');
44
+ return r.data;
45
+ }
46
+
47
+ function noteTrade(kind, data) {
48
+ try { notify({ kind, data, at: Date.now() }); } catch {}
49
+ }
50
+
51
+ function notConnected() {
52
+ return { ok: false, error: 'MT5 köprüsü bağlı değil — terminal açık mı? (panelde ⟳ ile yeniden bağlan)' };
53
+ }
54
+
55
+ const definitions = NAMES.map((name) => {
56
+ const specs = {
57
+ mt5_status: {
58
+ description:
59
+ 'MT5 (MetaTrader 5) köprü durumunu döndürür: köprü çalışıyor mu, terminale bağlı mı, terminal/hesap özeti. Her finance turunda VEYA bağlantı sorunlarında ilk çağrı.',
60
+ parameters: { type: 'object', properties: {} },
61
+ },
62
+ mt5_account: {
63
+ description:
64
+ 'MT5 hesap özeti: bakiye, özkaynak (equity), yüzen kâr/zarar, marj, serbest marj, marj seviyesi, kaldıraç, para birimi, hesap no/sunucu.',
65
+ parameters: { type: 'object', properties: {} },
66
+ },
67
+ mt5_market: {
68
+ description:
69
+ 'Verilen semboller için canlı fiyat: bid/ask, spread, digit, point. symbols verilmezse izleme listesi (watchlist) kullanılır. Örn: ["EURUSD","XAUUSD"].',
70
+ parameters: {
71
+ type: 'object',
72
+ properties: {
73
+ symbols: { type: 'array', items: { type: 'string' }, description: 'Sembol listesi (boşsa watchlist)' },
74
+ },
75
+ },
76
+ },
77
+ mt5_positions: {
78
+ description: 'Açık pozisyonlar: ticket, sembol, yön, lot, açılış fiyatı, güncel fiyat, SL/TP, yüzen kâr/zarar, açılış zamanı.',
79
+ parameters: { type: 'object', properties: {} },
80
+ },
81
+ mt5_orders: {
82
+ description: 'Bekleyen emirler (pending orders): ticket, sembol, tip (limit/stop), lot, fiyat, SL/TP.',
83
+ parameters: { type: 'object', properties: {} },
84
+ },
85
+ mt5_history: {
86
+ description: 'Kapanmış işlemler (deals) özeti: son N günde kapanan pozisyonlar, kâr/zarar, komisyon, swap. days=1 → son 24 saat.',
87
+ parameters: {
88
+ type: 'object',
89
+ properties: { days: { type: 'number', description: 'Kaç gün geriye bakılsın (varsayılan 1, en fazla 90)' } },
90
+ },
91
+ },
92
+ mt5_trade: {
93
+ description:
94
+ 'PİYASA EMRİ AÇAR: mt5_trade {symbol, side:"buy"|"sell", volume, sl?, tp?, comment?}. Lot limiti ve max pozisyon sayısı sistem tarafından zorlanır. Otomatik işlem anahtarı kapalıysa reddedilir. SL/TP vermek ŞIDDETLİ önerilir.',
95
+ parameters: {
96
+ type: 'object',
97
+ properties: {
98
+ symbol: { type: 'string', description: 'Örn: EURUSD' },
99
+ side: { type: 'string', enum: ['buy', 'sell'] },
100
+ volume: { type: 'number', description: 'Lot (max lot sınırına tabi)' },
101
+ sl: { type: 'number', description: 'Stop loss fiyatı (0 = yok)' },
102
+ tp: { type: 'number', description: 'Take profit fiyatı (0 = yok)' },
103
+ comment: { type: 'string', description: 'Kısa işlem notu' },
104
+ },
105
+ required: ['symbol', 'side', 'volume'],
106
+ },
107
+ },
108
+ mt5_close: {
109
+ description: 'Açık pozisyonu kapatır: mt5_close {ticket, volume?}. volume verilirse kısmi kapatır. Risk yönetimi için kullanılır.',
110
+ parameters: {
111
+ type: 'object',
112
+ properties: {
113
+ ticket: { type: 'number', description: 'Pozisyon ticket no' },
114
+ volume: { type: 'number', description: 'Kapatılacak lot (boşsa tamamı)' },
115
+ },
116
+ required: ['ticket'],
117
+ },
118
+ },
119
+ mt5_modify: {
120
+ description: 'Pozisyonun SL/TP seviyelerini günceller: mt5_modify {ticket, sl, tp}. SL’siz pozisyon bırakmamak için kullan.',
121
+ parameters: {
122
+ type: 'object',
123
+ properties: {
124
+ ticket: { type: 'number' },
125
+ sl: { type: 'number', description: 'Yeni SL (0 = kaldır)' },
126
+ tp: { type: 'number', description: 'Yeni TP (0 = kaldır)' },
127
+ },
128
+ required: ['ticket'],
129
+ },
130
+ },
131
+ mt5_pending: {
132
+ description:
133
+ 'BEKLEYEN EMİR koyar: mt5_pending {symbol, type:"buy_limit"|"sell_limit"|"buy_stop"|"sell_stop", volume, price, sl?, tp?}. Otomatik işlem anahtarına tabidir.',
134
+ parameters: {
135
+ type: 'object',
136
+ properties: {
137
+ symbol: { type: 'string' },
138
+ type: { type: 'string', enum: ['buy_limit', 'sell_limit', 'buy_stop', 'sell_stop'] },
139
+ volume: { type: 'number' },
140
+ price: { type: 'number' },
141
+ sl: { type: 'number' },
142
+ tp: { type: 'number' },
143
+ },
144
+ required: ['symbol', 'type', 'volume', 'price'],
145
+ },
146
+ },
147
+ mt5_cancel: {
148
+ description: 'Bekleyen emri iptal eder: mt5_cancel {ticket}.',
149
+ parameters: {
150
+ type: 'object',
151
+ properties: { ticket: { type: 'number' } },
152
+ required: ['ticket'],
153
+ },
154
+ },
155
+ };
156
+ const s = specs[name];
157
+ return { type: 'function', function: { name, ...s } };
158
+ });
159
+
160
+ const handlers = {
161
+ async mt5_status() {
162
+ const st = mt5.status();
163
+ return { ok: true, ...st };
164
+ },
165
+ async mt5_account() {
166
+ if (!mt5.running) return notConnected();
167
+ const data = await bcall('account', {}, 8000);
168
+ return { ok: true, ...data };
169
+ },
170
+ async mt5_market(args) {
171
+ if (!mt5.running) return notConnected();
172
+ const cfg = getCfg();
173
+ let syms = Array.isArray(args.symbols) ? args.symbols.map((s) => String(s).trim()).filter(Boolean) : [];
174
+ if (!syms.length) syms = Array.isArray(cfg.symbols) ? cfg.symbols : [];
175
+ const data = await bcall('symbols', { symbols: syms }, 10000);
176
+ return { ok: true, symbols: data && data.symbols ? data.symbols : [] };
177
+ },
178
+ async mt5_positions() {
179
+ if (!mt5.running) return notConnected();
180
+ const data = await bcall('positions', {}, 8000);
181
+ const list = (data && data.positions) || [];
182
+ return { ok: true, count: list.length, positions: list };
183
+ },
184
+ async mt5_orders() {
185
+ if (!mt5.running) return notConnected();
186
+ const data = await bcall('orders', {}, 8000);
187
+ const list = (data && data.orders) || [];
188
+ return { ok: true, count: list.length, orders: list };
189
+ },
190
+ async mt5_history(args) {
191
+ if (!mt5.running) return notConnected();
192
+ let days = Number(args.days);
193
+ if (!isFinite(days) || days <= 0) days = 1;
194
+ days = Math.min(days, 90);
195
+ const data = await bcall('deals', { days }, 12000);
196
+ const deals = (data && data.deals) || [];
197
+ /* hızlı özet: giriş/çıkış eşleşmesi yapmadan net realized P/L */
198
+ let profit = 0;
199
+ let wins = 0;
200
+ let losses = 0;
201
+ for (const dl of deals) {
202
+ const p = Number(dl.profit) || 0;
203
+ if (String(dl.entry) === '1' || dl.entry === 1 || p !== 0) {
204
+ profit += p + (Number(dl.swap) || 0) + (Number(dl.commission) || 0);
205
+ if (p > 0) wins++;
206
+ else if (p < 0) losses++;
207
+ }
208
+ }
209
+ return {
210
+ ok: true,
211
+ days,
212
+ count: deals.length,
213
+ netProfit: Math.round(profit * 100) / 100,
214
+ wins,
215
+ losses,
216
+ deals: deals.slice(-60),
217
+ };
218
+ },
219
+ async mt5_trade(args) {
220
+ const cfg = getCfg();
221
+ if (!cfg.allowTrading) {
222
+ return { ok: false, error: 'Otomatik işlem KAPALI — analiz/öneri modundasın. İşlem önerisini yaz, açma.' };
223
+ }
224
+ if (!mt5.running) return notConnected();
225
+ const side = String(args.side || '').toLowerCase();
226
+ if (side !== 'buy' && side !== 'sell') return { ok: false, error: "side 'buy' veya 'sell' olmalı" };
227
+ const symbol = String(args.symbol || '').trim().toUpperCase();
228
+ if (!symbol) return { ok: false, error: 'symbol gerekli' };
229
+ const maxLot = Number(cfg.maxLot) || 0.1;
230
+ const vol = clampLot(args.volume, maxLot);
231
+ if (!vol) return { ok: false, error: 'volume gerekli (pozitif sayı)' };
232
+ const pos = await bcall('positions', {}, 8000);
233
+ const maxPos = Number(cfg.maxPositions) || 3;
234
+ const openCount = ((pos && pos.positions) || []).length;
235
+ if (openCount >= maxPos) {
236
+ return { ok: false, error: `max eşzamanlı pozisyon dolu (${openCount}/${maxPos}) — önce bir pozisyon kapat` };
237
+ }
238
+ const data = await bcall('market', {
239
+ symbol,
240
+ side,
241
+ volume: vol,
242
+ sl: Number(args.sl) || 0,
243
+ tp: Number(args.tp) || 0,
244
+ deviation: 20,
245
+ comment: String(args.comment || 'Beast').slice(0, 26),
246
+ }, 20000);
247
+ noteTrade('trade', { symbol, side, volume: vol, result: data && data.result });
248
+ return { ok: true, opened: { symbol, side, volume: vol, sl: Number(args.sl) || 0, tp: Number(args.tp) || 0 }, result: data && data.result };
249
+ },
250
+ async mt5_close(args) {
251
+ const cfg = getCfg();
252
+ if (!mt5.running) return notConnected();
253
+ const ticket = Number(args.ticket);
254
+ if (!ticket) return { ok: false, error: 'ticket gerekli' };
255
+ let vol = Number(args.volume) || 0;
256
+ if (vol > 0) {
257
+ const maxLot = Number(cfg.maxLot) || 0.1;
258
+ if (vol > maxLot) return { ok: false, error: `kısmi kapatma lotu limiti aşıyor (max ${maxLot})` };
259
+ }
260
+ const data = await bcall('close', { ticket, volume: vol || 0 }, 20000);
261
+ noteTrade('close', { ticket, volume: vol || 'all', result: data && data.result });
262
+ return { ok: true, result: data && data.result };
263
+ },
264
+ async mt5_modify(args) {
265
+ if (!mt5.running) return notConnected();
266
+ const ticket = Number(args.ticket);
267
+ if (!ticket) return { ok: false, error: 'ticket gerekli' };
268
+ const data = await bcall('modify', {
269
+ ticket,
270
+ sl: Number(args.sl) || 0,
271
+ tp: Number(args.tp) || 0,
272
+ }, 20000);
273
+ noteTrade('modify', { ticket, sl: Number(args.sl) || 0, tp: Number(args.tp) || 0 });
274
+ return { ok: true, result: data && data.result };
275
+ },
276
+ async mt5_pending(args) {
277
+ const cfg = getCfg();
278
+ if (!cfg.allowTrading) {
279
+ return { ok: false, error: 'Otomatik işlem KAPALI — bekleyen emir de açılamaz. Öneriyi yaz.' };
280
+ }
281
+ if (!mt5.running) return notConnected();
282
+ const maxLot = Number(cfg.maxLot) || 0.1;
283
+ const vol = clampLot(args.volume, maxLot);
284
+ if (!vol) return { ok: false, error: 'volume gerekli' };
285
+ const data = await bcall('pending', {
286
+ symbol: String(args.symbol || '').trim().toUpperCase(),
287
+ type: String(args.type || '').toLowerCase(),
288
+ volume: vol,
289
+ price: Number(args.price) || 0,
290
+ sl: Number(args.sl) || 0,
291
+ tp: Number(args.tp) || 0,
292
+ comment: String(args.comment || 'Beast').slice(0, 26),
293
+ }, 20000);
294
+ noteTrade('pending', { symbol: args.symbol, type: args.type, volume: vol });
295
+ return { ok: true, result: data && data.result };
296
+ },
297
+ async mt5_cancel(args) {
298
+ if (!mt5.running) return notConnected();
299
+ const ticket = Number(args.ticket);
300
+ if (!ticket) return { ok: false, error: 'ticket gerekli' };
301
+ const data = await bcall('cancel', { ticket }, 20000);
302
+ noteTrade('cancel', { ticket });
303
+ return { ok: true, result: data && data.result };
304
+ },
305
+ };
306
+
307
+ module.exports = { definitions, handlers, NAMES, setConfig, setNotify };
package/src/agent/llm.js CHANGED
@@ -573,7 +573,9 @@ async function chatStreamAuto(sel, body, opts = {}) {
573
573
  let last = first;
574
574
  for (let i = 0; i < 2; i++) {
575
575
  const msgs2 = body.messages.slice();
576
- msgs2.push({ role: 'assistant', content: full });
576
+ const asst = { role: 'assistant', content: full };
577
+ if (first.reasoning) asst.reasoning_content = first.reasoning; /* thinking geri-besleme */
578
+ msgs2.push(asst);
577
579
  msgs2.push({ role: 'user', content: CONTINUE_PROMPT });
578
580
  let cont;
579
581
  try {