beast-agent 2.63.1 → 2.63.2

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.63.1",
4
+ "version": "2.63.2",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
package/src/agent/bots.js CHANGED
@@ -130,7 +130,25 @@ function ensureDefaultBots() {
130
130
  loadRegistry();
131
131
  let created = false;
132
132
  for (const def of DEFAULT_BOT_SEEDS) {
133
- if (REG.bots.some((b) => b && b.id === def.id)) continue;
133
+ const existing = REG.bots.find((b) => b && b.id === def.id);
134
+ if (existing) {
135
+ /* VARSAYILAN PROMPT GÜNCELLEMESİ (tek seferlik ek): eski sürümde
136
+ oluşmuş Tool botunun promptunda mql5/edit_file/skill vurgusu yoksa
137
+ eksik parçalar EKLENİR — kullanıcı düzenlemesi ezilmez. */
138
+ if (def.id === 'tool') {
139
+ const p = String(existing.prompt || '');
140
+ const add = [];
141
+ if (!/skill\(["']mql5["']\)/.test(p)) add.push('MQL5/gösterge/EA işlerinde ÖNCE skill("mql5") oku ve prosedürüne uy.');
142
+ if (!/edit_file/.test(p)) add.push('Var olan dosyayı edit_file ile GÜNCELLE; yeniyi write_file ile yaz; içerik aramada grep, dosya adı aramada glob kullan.');
143
+ if (!/skill\(["']tool-yazma["']\)/.test(p)) add.push('Tool yazmadan önce skill("tool-yazma") oku ve sözleşmeye birebir uy.');
144
+ if (add.length) {
145
+ existing.prompt = (p ? p + ' ' : '') + add.join(' ');
146
+ created = true;
147
+ logChange(def.id, 'varsayılan prompt ekleri eklendi (mql5/edit_file/skill)');
148
+ }
149
+ }
150
+ continue;
151
+ }
134
152
  if (REG.bots.length >= MAX_BOTS) break;
135
153
  const bot = {
136
154
  id: def.id,
@@ -2074,7 +2074,7 @@ class Engine {
2074
2074
  '- Emin olmadığında İŞLEM YOK — "BEKLE: <sebep>" yaz. Sık işlem tek başına iyi işlem değil; ama onaylı fırsatta HIZLI gir, kârı hızlı topla.\n' +
2075
2075
  '- 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. Sık işlem için lotu değil, TUR SIKLIĞINI ve kısmi kâr alımını kullan.\n' +
2076
2076
  (session && session.financeLearn ? `SEMBOL BAZLI ÖĞRENME HAFIZAN (otomatik istatistik + kendi derslerin — kararlarında kullan):\n${session.financeLearn}\n` : '') +
2077
- 'ÖĞRENME DÖNGÜSÜ (ZORUNLU): her kapanıştan (özellikle stop/TP sonrası) hemen sonra mt5_ogrenme {action:"add", symbol, text, kind} ile o sembolün dersini yaz (ne işe yaradı/yaramadı, hata mı, kural mı); yeni işlem kararından önce mt5_ogrenme {action:"stats", symbol} ya da list ile o sembolün geçmişini oku — aynı hatayı tekrarlama, işleyen deseni kullan. Geçersiz dersi remove/clear ile sil.\n' +
2077
+ 'ÖĞRENME DÖNGÜSÜ (ZORUNLU): her kapanıştan (özellikle stop/TP sonrası) hemen sonra mt5_ogrenme {action:"add", symbol, text, kind} ile o sembolün dersini yaz (ne işe yaradı/yaramadı, hata mı, kural mı); yeni işlem kararından önce mt5_ogrenme {action:"stats", symbol} ya da list ile o sembolün geçmişini oku (kalıcı özet + son dersler) — aynı hatayı tekrarlama, işleyen deseni kullan. Hafıza kendini sadeleştirir: eski dersler otomatik özetlenir ve süresi geçenler unutulur; gerekirse mt5_ogrenme {action:"compact", symbol} ile hemen özetlet, geçersiz dersi remove/clear ile sil.\n' +
2078
2078
  (session && session.financeStrategy ? `SAHİBİNİN STRATEJİ NOTU (önceliklidir):\n${session.financeStrategy}\n` : '') +
2079
2079
  (session && session.financeShadow ? 'SHADOW MOD AKTİF: mt5_trade/mt5_pending emir GÖNDERMEZ — kararını teziyle raporla; gerçek işlem açılmaz (karar günlüğe yazılır).\n' : '') +
2080
2080
  (session && session.financeDigest ? `İŞLEM PERFORMANS GEÇMİŞİN (kendi kayıtların — kararlarında ders çıkar):\n${session.financeDigest}\n` : '') +
@@ -3845,15 +3845,17 @@ class Engine {
3845
3845
  /* KİŞİSEL TOOLLAR: %APPDATA%\beast\tools\<ad>\ — kullanıcı yazmışsa
3846
3846
  TÜM oturumlara (BEAST FINANCE dahil) tool__<ad> olarak açılır */
3847
3847
  toolsList = [...toolsList, ...customtools.definitions()];
3848
- /* Beast Finance: skill aracı da açık — kataloğun SKILL.md gövdesi
3849
- modele açılır (skill handler'ı zaten tüm oturumlar için çalışır,
3850
- definition yalnız BC'de vardı; finance chat copilot + trader ikisi de)
3851
- Not: BC oturumları zaten opencode registry'sinden skill'i görür. */
3852
- if (session && session.finance) {
3848
+ /* SKILL ARACI: kurulu SKILL.md gövdesini modele açar — finance oturumları
3849
+ kadar BOT oturumları (Tool botu: tool-yazma/mql5) ve arka plan ajanları
3850
+ da promptlarında skill() kullanır. Tanım yalnız finance'a verilince bot
3851
+ skill çağrısı yapamıyor, tur rapor üretmeden boşa düşüyordu. */
3852
+ if (session && (session.finance || session.botId || session.bgJob)) {
3853
3853
  const skillDef = (opencode.toolmap.definitions() || []).find(
3854
3854
  (d) => d && d.function && d.function.name === 'skill'
3855
3855
  );
3856
- if (skillDef) toolsList = [...toolsList, skillDef];
3856
+ if (skillDef && !toolsList.some((t) => t && t.function && t.function.name === 'skill')) {
3857
+ toolsList = [...toolsList, skillDef];
3858
+ }
3857
3859
  }
3858
3860
  /* AJAN DM: koşan ajanlara (arka plan işleri + finance), ana sohbete VE
3859
3861
  bot sohbetlerine verilir — botlar birbirine iş atar/cevap verir
@@ -4913,6 +4915,7 @@ const skills = require('./skills');
4913
4915
  requesterTitle: who,
4914
4916
  responderTitle: target.name || 'Tool botu',
4915
4917
  task: task.slice(0, 300),
4918
+ text,
4916
4919
  before,
4917
4920
  at: Date.now(),
4918
4921
  });
@@ -4964,14 +4967,16 @@ const skills = require('./skills');
4964
4967
  }
4965
4968
 
4966
4969
  /* Tool botunun turu bitti: bekleyen isteklerin raporunu AJAN DM paneline
4967
- ve isteyen oturuma düşür. _run finally'den çağrılır (tüm bitiş yolları). */
4970
+ ve isteyen oturuma düşür. _run finally'den çağrılır (tüm bitiş yolları).
4971
+ Tur metin rapor üretmeden öldüyse: (1) tur hiç iş yapmadıysa TEK kez
4972
+ sessizce yeniden dene (geçici model/ağ hatası), (2) yine olmazsa araç
4973
+ izini + hata mesajını rapora koy ki isteyen NE olduğunu anlasın. */
4968
4974
  _toolRequestFinished(sid) {
4969
4975
  try {
4970
4976
  const key = String(sid || '');
4971
4977
  if (!this._toolPending || !this._toolPending.size) return;
4972
4978
  const list = this._toolPending.get(key);
4973
4979
  if (!list || !list.length) return;
4974
- this._toolPending.delete(key);
4975
4980
  const s = this.cache.get(key) || this._load(key);
4976
4981
  const reply = (() => {
4977
4982
  for (let i = s.messages.length - 1; i >= 0; i--) {
@@ -4983,10 +4988,51 @@ const skills = require('./skills');
4983
4988
  }
4984
4989
  return '';
4985
4990
  })();
4991
+ /* TEST/İZ: isteğin başladığı noktadan sonra araç çağrısı/sonucu var mı? */
4992
+ const activity = (from) => {
4993
+ let n = 0;
4994
+ for (let i = Math.max(0, Number(from) || 0); i < s.messages.length; i++) {
4995
+ const m = s.messages[i];
4996
+ if (m && ((m.role === 'assistant' && Array.isArray(m.tool_calls) && m.tool_calls.length) || m.role === 'tool')) n++;
4997
+ }
4998
+ return n;
4999
+ };
5000
+ /* GEÇİCİ ÖLÜM: tek istek + hiç araç izi + metin yok → tek kez yeniden dene */
5001
+ if (!reply && list.length === 1 && !list[0].retried && list[0].text && activity(list[0].before) === 0) {
5002
+ list[0].retried = true;
5003
+ this._toolPending.set(key, list);
5004
+ try {
5005
+ const ok = this.send(key, { text: '[YENİDEN DENEME — önceki tur iş yapmadan düştü; isteği baştan yürüt]\n' + String(list[0].text) });
5006
+ if (ok) return;
5007
+ } catch {}
5008
+ }
5009
+ this._toolPending.delete(key);
5010
+ /* METİN RAPOR YOKSA: son turun araç izini özetle — teşhis kaybolmasın */
5011
+ const failDetail = (() => {
5012
+ if (reply) return '';
5013
+ const L = [];
5014
+ for (let i = s.messages.length - 1; i >= 0 && L.length < 8; i--) {
5015
+ const m = s.messages[i];
5016
+ if (!m) continue;
5017
+ if (m.role === 'tool') {
5018
+ const t = typeof m.content === 'string' ? m.content : '';
5019
+ if (t.trim()) L.unshift('araç sonucu: ' + t.replace(/\s+/g, ' ').slice(0, 180));
5020
+ } else if (m.role === 'assistant' && Array.isArray(m.tool_calls) && m.tool_calls.length) {
5021
+ L.unshift('çağrılan araçlar: ' + m.tool_calls.map((c) => (c && c.function && c.function.name) || '?').join(', '));
5022
+ } else if (m.role === 'assistant' && typeof m.content === 'string' && m.content.trim()) {
5023
+ L.unshift('son metin: ' + stripAiDashes(m.content.trim()).replace(/\s+/g, ' ').slice(0, 180));
5024
+ } else if (m.role === 'user') {
5025
+ break;
5026
+ }
5027
+ }
5028
+ return L.join('\n');
5029
+ })();
4986
5030
  for (const req of list) {
4987
5031
  const body = reply
4988
5032
  ? `[TOOL BOTU RAPORU — ${req.id}]\n${reply}`
4989
- : `[TOOL BOTU] ${req.id} numaralı istek tamamlanamadı (tur yarıda kesildi ya da metin rapor üretilemedi). Tool botu sohbetinden ayrıntıya bakabilirsin.`;
5033
+ : `[TOOL BOTU] ${req.id} numaralı istek tamamlanamadı (tur yarıda kesildi ya da metin rapor üretilemedi).\n` +
5034
+ (failDetail ? 'Ne oldu:\n' + failDetail + '\n' : '') +
5035
+ 'Tool botu sohbetinden ayrıntıya bakabilirsin.';
4990
5036
  const dm = {
4991
5037
  at: new Date().toISOString(),
4992
5038
  from: key,
@@ -7081,8 +7127,14 @@ Engine.prototype._agentDmDeliver = function (dm, target, opts = {}) {
7081
7127
  dm: true,
7082
7128
  wake: !!opts.wake,
7083
7129
  };
7084
- /* bot↔bot sigorta: boş bot oturumunu her DM'de yeni turla uyandırma */
7085
- if (sess && sess.botId && !this.isBusy(m) && !this._agentDmWakeAllowed(dm.from, m)) {
7130
+ /* bot↔bot sigorta: boş bot oturumunu her DM'de yeni turla uyandırma.
7131
+ TOOL BOTU İSTİSNA (yalnız bot OLMAYAN gönderenden): ajan (finance/paralel
7132
+ sohbet) → Tool DM'i gerçek görevdir, beklemeden uyandırılır; bot↔bot
7133
+ ping-pong'u ise bütçeye tabidir (sonsuz tur fırtınası korunur). */
7134
+ const fromSess = this.cache.get(String(dm.from)) || null;
7135
+ const fromIsBot = !!(fromSess && fromSess.botId);
7136
+ const isToolBot = sess && String(sess.botId || '') === 'tool' && !fromIsBot;
7137
+ if (sess && sess.botId && !isToolBot && !this.isBusy(m) && !this._agentDmWakeAllowed(dm.from, m)) {
7086
7138
  this._agentDmBacklog = this._agentDmBacklog || new Map();
7087
7139
  const q = this._agentDmBacklog.get(m) || [];
7088
7140
  q.push({ text, ...(dm.image ? { image: String(dm.image) } : {}) });
@@ -276,11 +276,11 @@ const definitions = NAMES.map((name) => {
276
276
  },
277
277
  mt5_ogrenme: {
278
278
  description:
279
- 'SEMBOL BAZLI ÖĞRENME HAFIZASI — Beast Finance sürekli öğrenir: mt5_ogrenme {action:"list"|"add"|"remove"|"clear"|"stats", symbol?, text?, kind?, tags?, id?, ids?, all?}. Her sembolün KENDİ istatistiği (işlem/kazanç/kayıp, net, kâr yakalama, kâr geri verme) OTOMATİK birikir; ayrıca işlemlerden çıkardığın DERSLERİ sen kaydedersin. KURALLAR: (1) Her kapanıştan sonra (özellikle stop/tp sonrası) sembol için tek cümle ders yaz: hangi setup işe yaradı/yaramadı, saat/seans, SL yeri, hata mı hata yok mu — kind:"pattern"|"mistake"|"rule"|"observation". (2) Yeni işlem kararından ÖNCE ilgili sembolün list/stats çıktısını oku; aynı hatayı tekrarlama, işleyen deseni kullan. (3) Yanlış çıkan/genel geçersiz dersi remove/clear ile sil. action:"list" symbol verilmezse tüm sembollerin özetini döner. Ör: {action:"add", symbol:"XAUUSD", text:"Londra açılışında M5 EMA50 üstü momentum girişleri iyi çalışıyor", kind:"pattern"}.',
279
+ 'SEMBOL BAZLI ÖĞRENME HAFIZASI — Beast Finance sürekli öğrenir: mt5_ogrenme {action:"list"|"add"|"remove"|"clear"|"stats"|"compact"|"forget", symbol?, text?, kind?, tags?, id?, ids?, all?}. Her sembolün KENDİ istatistiği (işlem/kazanç/kayıp, net, kâr yakalama, kâr geri verme) OTOMATİK birikir; ayrıca işlemlerden çıkardığın DERSLERİ sen kaydedersin. KURALLAR: (1) Her kapanıştan sonra (özellikle stop/tp sonrası) sembol için tek cümle ders yaz: hangi setup işe yaradı/yaramadı, saat/seans, SL yeri, hata mı hata yok mu — kind:"pattern"|"mistake"|"rule"|"observation". (2) Yeni işlem kararından ÖNCE ilgili sembolün list/stats çıktısını oku; aynı hatayı tekrarlama, işleyen deseni kullan. (3) Yanlış çıkan/genel geçersiz dersi remove/clear ile sil. Aynı ders tekrar yazılamaz ve sembol başına 24 saatte en fazla 12 ders kaydedilir. HAFIZA KENDİNİ SADELEŞTİRİR (opencode tarzı compaction): ham dersler birikince (24+) ESKİ dersler OTOMATİK olarak modele özetlettirilir ve tek KALICI ÖZETE sıkıştırılır — ham yalnız son dersler kalır; 30 günden eski dersler, 90 günden eski işlem kayıtları ve 120 gün hareketsiz semboller otomatik unutulur. Gerekirse compact {symbol} ile hemen özetlet, forget ile süresi geçenleri temizle. action:"list" symbol verilmezse tüm sembollerin özetini döner (kalıcı özet dahil). Ör: {action:"add", symbol:"XAUUSD", text:"Londra açılışında M5 EMA50 üstü momentum girişleri iyi çalışıyor", kind:"pattern"}.',
280
280
  parameters: {
281
281
  type: 'object',
282
282
  properties: {
283
- action: { type: 'string', enum: ['list', 'add', 'remove', 'clear', 'stats'] },
283
+ action: { type: 'string', enum: ['list', 'add', 'remove', 'clear', 'stats', 'compact', 'forget'] },
284
284
  symbol: { type: 'string', description: 'Sembol (ör. XAUUSD) — öğrenme sembol bazlıdır' },
285
285
  text: { type: 'string', description: 'add: öğrenilen ders/desen (kısa, net, tek cümle)' },
286
286
  kind: { type: 'string', enum: ['pattern', 'mistake', 'rule', 'observation'], description: 'add: kayıt türü (varsayılan observation)' },
@@ -600,7 +600,18 @@ const handlers = {
600
600
  const all = args.all === true || String(args.all || '').toLowerCase() === 'true';
601
601
  return learningApi.clear({ ids, symbol, all });
602
602
  }
603
- return { ok: false, error: 'action: list|add|remove|clear|stats' };
603
+ if (action === 'compact') {
604
+ /* OPENCODE TARZI ÖZETLEME: eski dersler modele özetlettirilir, ham yalnız
605
+ son dersler kalır — bellek sadeleşir (normalde otomatik de çalışır) */
606
+ if (!symbol) return { ok: false, error: 'compact için symbol gerekli' };
607
+ if (typeof learningApi.compact !== 'function') return { ok: false, error: 'özetleme kullanılamıyor' };
608
+ return await learningApi.compact({ symbol });
609
+ }
610
+ if (action === 'forget') {
611
+ if (typeof learningApi.forget !== 'function') return { ok: false, error: 'temizleme kullanılamıyor' };
612
+ return learningApi.forget();
613
+ }
614
+ return { ok: false, error: 'action: list|add|remove|clear|stats|compact|forget' };
604
615
  },
605
616
  async mt5_ea(args) {
606
617
  if (!mt5.running) return notConnected();
package/src/main.js CHANGED
@@ -2356,6 +2356,7 @@ function botToolSet(cfg) {
2356
2356
  'tool_request', /* CEPHANE: eksik aracı TOOL botuna yazdırma hakkı HER botta */
2357
2357
  'channel_send', /* İLK MESAJ: allow listteki kişilere kendiliğinden yazma hakkı HER botta */
2358
2358
  'agent_dm', /* BOTLAR ARASI DM: botlar birbirine iş atar/cevap verir (ör. Beast → Tool) */
2359
+ 'skill', /* SKILL KATALOĞU: botlar SKILL.md gövdesini skill() ile okur (Tool botu: tool-yazma/mql5) */
2359
2360
  ]);
2360
2361
  if (s.web_search) { set.add('web_search'); set.add('http_fetch'); set.add('webfetch'); set.add('deep_search'); }
2361
2362
  if (s.browser) {
@@ -2363,7 +2364,7 @@ function botToolSet(cfg) {
2363
2364
  }
2364
2365
  if (s.email) { set.add('email_list'); set.add('email_read'); set.add('email_send'); }
2365
2366
  if (s.run_command) {
2366
- for (const t of ['run_command', 'python_run', 'read_file', 'write_file', 'list_dir', 'computer_look', 'computer_act',
2367
+ for (const t of ['run_command', 'python_run', 'read_file', 'write_file', 'edit_file', 'grep', 'glob', 'list_dir', 'computer_look', 'computer_act',
2367
2368
  'git_commit', 'git_diff_review', 'git_pr_create', 'repo_map', 'repo_symbols', 'xlsx_read', 'xlsx_write', 'xlsx_edit']) set.add(t);
2368
2369
  }
2369
2370
  if (s.memory) { set.add('memory_write'); set.add('user_write'); set.add('memory_search'); set.add('memory_hygiene'); }
@@ -4323,6 +4324,43 @@ function detachBrowser() {
4323
4324
  browser.attached = false;
4324
4325
  }
4325
4326
 
4327
+ /* TARAYICI YENİDEN BAŞLATMA: takılan/kilitlenen sayfa için view + webContents
4328
+ tamamen kapatılır, aynı URL YENİ bir view'da açılır. persist:browser
4329
+ partition'ı silinmez → oturum/çerezler (Google, X vb.) korunur. */
4330
+ function restartBrowser() {
4331
+ if (!win || win.isDestroyed()) return { ok: false, error: 'pencere yok' };
4332
+ const wasOpen = browser.open;
4333
+ let url = '';
4334
+ try {
4335
+ if (browser.view && browser.view.webContents && !browser.view.webContents.isDestroyed()) {
4336
+ url = browser.view.webContents.getURL();
4337
+ }
4338
+ } catch {}
4339
+ detachBrowser();
4340
+ try {
4341
+ const oldWc = browser.view && browser.view.webContents;
4342
+ if (oldWc && !oldWc.isDestroyed()) {
4343
+ try { oldWc.close(); } catch { try { oldWc.destroy(); } catch {} }
4344
+ /* takılı renderer süreci close()'u geciktirebilir — güvence: kısa süre
4345
+ sonra hâlâ duruyorsa zorla yok et (yeni view'ı asla kilitlemez) */
4346
+ setTimeout(() => { try { if (!oldWc.isDestroyed()) oldWc.destroy(); } catch {} }, 1500);
4347
+ }
4348
+ } catch {}
4349
+ browser.view = null;
4350
+ browser.started = false;
4351
+ browser.lastDialog = null;
4352
+ if (wasOpen) {
4353
+ ensureBrowser();
4354
+ browser.started = true;
4355
+ browser.view.webContents.loadURL(url || BROWSER_START_URL).catch(() => {});
4356
+ layoutBrowser();
4357
+ const [w] = win.getContentSize();
4358
+ browserEmit({ open: true, width: browserShownWidth(w), url: url || BROWSER_START_URL, restarted: true });
4359
+ }
4360
+ blog('restart', 'tarayıcı yeniden başlatıldı' + (url ? ' — ' + url.slice(0, 90) : ''));
4361
+ return { ok: true, url };
4362
+ }
4363
+
4326
4364
  function setBrowserOpen(v, forceVisible) {
4327
4365
  if (!win || win.isDestroyed()) return;
4328
4366
  const want = !!v;
@@ -8399,6 +8437,7 @@ ipcMain.handle('browser:ctrl', (_e, action) => {
8399
8437
  setBrowserOpen(false);
8400
8438
  return { ok: true };
8401
8439
  }
8440
+ if (action === 'restart') return restartBrowser();
8402
8441
  const wc = browser.view && browser.view.webContents;
8403
8442
  if (!wc) return { ok: false };
8404
8443
  try {
@@ -11280,8 +11319,21 @@ function finLimitsSet(patch, ctx) {
11280
11319
  kazanç/kayıp, net, MFE/MAE, kâr geri verme) (2) ajanın yazdığı dersler.
11281
11320
  Trader turunda bu özet sistem promptuna gömülür — sistem kendi geçmişinden
11282
11321
  öğrenir; her kapanış otomatik işlenir, dersleri ajan mt5_ogrenme ile yazar. */
11283
- const FIN_LEARN_MAX_NOTES = 120;
11322
+ const FIN_LEARN_MAX_NOTES = 60; /* sembol başına ders tavanı */
11284
11323
  const FIN_LEARN_MAX_TRADES = 60;
11324
+ const FIN_LEARN_MAX_SYMBOLS = 60; /* toplam sembol tavanı — depo sınırsız büyümesin */
11325
+ const FIN_LEARN_MAX_NOTES_DAY = 12; /* sembol başına 24 saatte en fazla ders — ajan seli depoyu zehirlemesin */
11326
+ /* SÜREKLİ UNUTMA (bellek hijyeni): eski kayıtlar otomatik düşer — depo büyüyüp
11327
+ zehirlenmez, yalnız GÜNCEL dersler ve son performans kalır. */
11328
+ const FIN_LEARN_NOTE_TTL_MS = 30 * 24 * 60 * 60 * 1000; /* 30 gün: eski ders unutulur */
11329
+ const FIN_LEARN_TRADE_TTL_MS = 90 * 24 * 60 * 60 * 1000; /* 90 gün: eski işlem kaydı düşer */
11330
+ const FIN_LEARN_SYMBOL_TTL_MS = 120 * 24 * 60 * 60 * 1000; /* 120 gün aktivite yoksa sembol kaydı silinir */
11331
+ /* ÖZETLEME (opencode tarzı compaction): ham dersler birikince ESKİ olanlar
11332
+ modele özetlettirilir → tek kalıcı özet; ham yalnız son dersler kalır. */
11333
+ const FIN_LEARN_COMPACT_AT = 24; /* not sayısı bunu aşınca arka planda otomatik özetle */
11334
+ const FIN_LEARN_KEEP_RECENT = 8; /* ham kalan son ders sayısı */
11335
+ const FIN_LEARN_SUMMARY_MAX = 800; /* kalıcı özet metin tavanı (karakter) */
11336
+ const FIN_LEARN_COMPACT_MIN = 4; /* elle özetleme için gereken en az eski ders */
11285
11337
 
11286
11338
  function finLearnEmptyStats() {
11287
11339
  return {
@@ -11291,6 +11343,70 @@ function finLearnEmptyStats() {
11291
11343
  };
11292
11344
  }
11293
11345
 
11346
+ /* Ders metni karşılaştırma anahtarı: büyük/küçük harf, noktalama ve boşluk
11347
+ farkları aynı ders sayılır — ajan aynı cümleyi kopyalayıp depoyu doldurmasın */
11348
+ function finLearnNoteKey(text) {
11349
+ return String(text || '')
11350
+ .toLowerCase()
11351
+ .replace(/[^\p{L}\p{N}\s]+/gu, ' ')
11352
+ .replace(/\s+/g, ' ')
11353
+ .trim()
11354
+ .slice(0, 140);
11355
+ }
11356
+
11357
+ /* sembolün son aktivitesi (kapanış / ders / eski istatistik) — tavan aşımında
11358
+ EN ESKİ semboller düşürülür; dokunulan (protectSym) sembol asla atılmaz */
11359
+ function finLearnActivity(e) {
11360
+ const last = (arr) => (Array.isArray(arr) && arr.length ? Number(arr[arr.length - 1].at) || 0 : 0);
11361
+ return Math.max(Number((e.stats || {}).lastAt) || 0, last(e.notes), last(e.trades));
11362
+ }
11363
+
11364
+ function finLearnPrune(learn, keep, protectSym) {
11365
+ const cap = Number.isFinite(Number(keep)) ? Number(keep) : FIN_LEARN_MAX_SYMBOLS;
11366
+ const keys = Object.keys(learn.symbols);
11367
+ if (keys.length <= cap) return 0;
11368
+ const protect = String(protectSym || '').trim().toUpperCase();
11369
+ const dropped = keys
11370
+ .sort((a, b) => {
11371
+ if (a === protect) return 1;
11372
+ if (b === protect) return -1;
11373
+ return finLearnActivity(learn.symbols[a]) - finLearnActivity(learn.symbols[b]);
11374
+ })
11375
+ .slice(0, keys.length - cap);
11376
+ for (const k of dropped) delete learn.symbols[k];
11377
+ return dropped.length;
11378
+ }
11379
+
11380
+ /* SÜREKLİ UNUTMA: TTL'i geçen ders/işlem kayıtları düşer; uzun süre hiç
11381
+ aktivite görmeyen sembol kaydı (istatistik dahil) silinir. Her yazma ve
11382
+ yüklemede çalışır → depo kendiliğinden sade kalır, birikme/zehirlenme olmaz. */
11383
+ function finLearnForget(learn, now) {
11384
+ const t = Number(now) || Date.now();
11385
+ const stat = { notes: 0, trades: 0, symbols: 0 };
11386
+ for (const [sym, e] of Object.entries(learn.symbols)) {
11387
+ if (!e || typeof e !== 'object') { delete learn.symbols[sym]; stat.symbols++; continue; }
11388
+ const notes = Array.isArray(e.notes) ? e.notes : [];
11389
+ e.notes = notes.filter((n) => n && t - (Number(n.at) || 0) <= FIN_LEARN_NOTE_TTL_MS);
11390
+ stat.notes += notes.length - e.notes.length;
11391
+ const trades = Array.isArray(e.trades) ? e.trades : [];
11392
+ e.trades = trades.filter((x) => x && t - (Number(x.at) || 0) <= FIN_LEARN_TRADE_TTL_MS);
11393
+ stat.trades += trades.length - e.trades.length;
11394
+ if (t - finLearnActivity(e) > FIN_LEARN_SYMBOL_TTL_MS) {
11395
+ delete learn.symbols[sym];
11396
+ stat.symbols += 1;
11397
+ }
11398
+ }
11399
+ return stat;
11400
+ }
11401
+
11402
+ /* TEK TEMİZLİK: önce TTL unutması, sonra sembol tavanı. protectSym (yeni
11403
+ dokunulan sembol) asla düşürülmez. */
11404
+ function finLearnTidy(learn, protectSym, now) {
11405
+ const forgot = finLearnForget(learn, now);
11406
+ const cap = finLearnPrune(learn, FIN_LEARN_MAX_SYMBOLS, protectSym);
11407
+ return { notes: forgot.notes, trades: forgot.trades, symbols: forgot.symbols + cap };
11408
+ }
11409
+
11294
11410
  function finLearnLoad() {
11295
11411
  if (financeState.learnReady) return financeState.learn;
11296
11412
  financeState.learnReady = true;
@@ -11305,21 +11421,128 @@ function finLearnLoad() {
11305
11421
  notes: Array.isArray(v.notes) ? v.notes.filter((n) => n && n.id && n.text).slice(-FIN_LEARN_MAX_NOTES) : [],
11306
11422
  trades: Array.isArray(v.trades) ? v.trades.filter(Boolean).slice(-FIN_LEARN_MAX_TRADES) : [],
11307
11423
  stats: v.stats && typeof v.stats === 'object' ? { ...finLearnEmptyStats(), ...v.stats } : finLearnEmptyStats(),
11424
+ /* KALICI ÖZET (opencode tarzı sıkıştırma): eski derslerin modele
11425
+ özetlettirilmiş hâli — ham dersler düşse de bilgi kalır */
11426
+ summary: v.summary && typeof v.summary === 'object' && String(v.summary.text || '').trim()
11427
+ ? {
11428
+ text: String(v.summary.text).replace(/\s+/g, ' ').trim().slice(0, FIN_LEARN_SUMMARY_MAX),
11429
+ at: Number(v.summary.at) || 0,
11430
+ covered: Math.max(0, Math.round(Number(v.summary.covered) || 0)),
11431
+ }
11432
+ : null,
11308
11433
  };
11309
11434
  }
11310
11435
  if (Number(raw.updatedAt)) learn.updatedAt = Number(raw.updatedAt);
11311
11436
  }
11312
11437
  } catch {}
11438
+ const dropped = finLearnTidy(learn); /* yükleme anında unut + tavan uygula */
11313
11439
  financeState.learn = learn;
11440
+ if (dropped.notes || dropped.trades || dropped.symbols) finLearnSave(); /* disk de hemen küçülsün */
11314
11441
  return learn;
11315
11442
  }
11316
11443
 
11317
11444
  function finLearnSave() {
11318
11445
  const learn = finLearnLoad();
11446
+ finLearnTidy(learn); /* her yazmada sürekli unutma */
11319
11447
  learn.updatedAt = Date.now();
11320
11448
  try { finWriteJson(finFile('ogrenme.json'), learn); } catch {}
11321
11449
  }
11322
11450
 
11451
+ /* ---------- OPENCODE TARZI ÖZETLEME (COMPACTION) ----------
11452
+ Ham dersler birikince ESKİ dersler modele özetlettirilir: çalışan desenler,
11453
+ kaçınılacak hatalar, kurallar tek KALICI ÖZET'e iner; ham olarak yalnız son
11454
+ dersler kalır. Böylece öğrenme hafızası hem küçük hem zehirsiz kalır —
11455
+ context penceresi sıkıştırmasının sembol bazlı hâli. */
11456
+ const FIN_LEARN_COMPACT_SYSTEM =
11457
+ 'Sen bir trading mentorüsün. Sana bir sembolün ESKİ ders notları ve mevcut özeti verilir. ' +
11458
+ 'Görevin: güncellenmiş KISA bir özet üretmek.\n' +
11459
+ 'KURALLAR: (1) Yalnız kalıcı değeri olan bilgiyi tut: çalışan desenler, kaçınılacak hatalar, ' +
11460
+ 'seans/setup kuralları, SL/TP davranışı. (2) Tekrarları birleştir; tarih/saat damgası, anlık ' +
11461
+ 'haber, geçici durum gibi bayat bilgiyi at. (3) Çelişen bilgide EN YENİ ve istatistikle ' +
11462
+ 'desteklenen kazanır. (4) Sayısal istatistikleri (işlem, kazanç %, net, kâr geri verme) koru. ' +
11463
+ '(5) En fazla ' + FIN_LEARN_SUMMARY_MAX + ' karakter; kısa maddeler ya da 2-4 akıcı cümle. ' +
11464
+ '(6) Çıktı SADECE özet metni olsun; başlık/etiket/açıklama yazma.';
11465
+
11466
+ const finLearnCompacting = new Set();
11467
+
11468
+ function finLearnSummaryPrompt(sym, e, oldNotes) {
11469
+ const st = { ...finLearnEmptyStats(), ...(e.stats || {}) };
11470
+ const wr = st.trades ? Math.round((st.wins / st.trades) * 100) : 0;
11471
+ const lines = [];
11472
+ lines.push('SEMBOL: ' + sym);
11473
+ lines.push(
11474
+ `OTOMATİK İSTATİSTİK: ${st.trades} işlem · %${wr} kazanç · net ${st.net >= 0 ? '+' : ''}${st.net}` +
11475
+ (st.givebacks ? ` · ${st.givebacks} kez kâr geri verildi` : '') +
11476
+ (st.streak >= 2 ? ` · ${st.streak} ardışık kayıp` : '')
11477
+ );
11478
+ if (e.summary && e.summary.text) lines.push('MEVCUT ÖZET:\n' + String(e.summary.text).slice(0, 1400));
11479
+ lines.push('ESKİ DERSLER (kronolojik — en yeni EN SONDA):');
11480
+ for (const n of oldNotes.slice(-80)) {
11481
+ lines.push(`- [${String(n.kind || 'observation')}] ${String(n.text || '').slice(0, 220)}`);
11482
+ }
11483
+ lines.push('Görev: yalnız kalıcı ve güncel dersleri koruyan yeni özet metnini yaz.');
11484
+ return lines.join('\n');
11485
+ }
11486
+
11487
+ /* Bir sembolü özetle: snapshot'taki ESKİ dersleri modele özetlet, onları düş,
11488
+ yerine kalıcı özet yaz. Model çalışırken gelen YENİ dersler ham kalır. */
11489
+ async function finLearnCompactSymbol(symbol, force) {
11490
+ const sym = String(symbol || '').trim().toUpperCase();
11491
+ if (!sym) return { ok: false, error: 'symbol gerekli' };
11492
+ if (finLearnCompacting.has(sym)) return { ok: false, error: 'bu sembol şu an özetleniyor' };
11493
+ if (!engine || !engine.sel) return { ok: false, error: 'özetleme için aktif model yok' };
11494
+ const e = finLearnLoad().symbols[sym];
11495
+ if (!e) return { ok: false, error: 'sembol kaydı yok: ' + sym };
11496
+ const oldNotes = e.notes.slice(0, Math.max(0, e.notes.length - FIN_LEARN_KEEP_RECENT));
11497
+ const min = force ? FIN_LEARN_COMPACT_MIN : FIN_LEARN_COMPACT_AT - FIN_LEARN_KEEP_RECENT;
11498
+ if (oldNotes.length < min) {
11499
+ return { ok: false, error: `özetlenecek yeterli eski ders yok (${oldNotes.length}/${min})` };
11500
+ }
11501
+ finLearnCompacting.add(sym);
11502
+ try {
11503
+ const ctrl = new AbortController();
11504
+ const kill = setTimeout(() => ctrl.abort(), 60000);
11505
+ let r = null;
11506
+ try {
11507
+ r = await require('./agent/llm').chatOnce(engine.sel, {
11508
+ messages: [
11509
+ { role: 'system', content: FIN_LEARN_COMPACT_SYSTEM },
11510
+ { role: 'user', content: finLearnSummaryPrompt(sym, e, oldNotes) },
11511
+ ],
11512
+ temperature: 0.2,
11513
+ }, { signal: ctrl.signal });
11514
+ } catch (err) {
11515
+ return { ok: false, error: 'özetleme çağrısı başarısız: ' + String((err && err.message) || err).slice(0, 120) };
11516
+ } finally {
11517
+ clearTimeout(kill);
11518
+ }
11519
+ const text = String((r && r.content) || '').replace(/\s+/g, ' ').trim().slice(0, FIN_LEARN_SUMMARY_MAX);
11520
+ if (!text) return { ok: false, error: 'özet üretilemedi (model yanıtı boş)' };
11521
+ /* yarışma koruması: yalnız snapshot'taki eski dersleri düşür */
11522
+ const fresh = finLearnLoad().symbols[sym];
11523
+ if (!fresh) return { ok: false, error: 'sembol kaydı kalmadı' };
11524
+ const drop = new Set(oldNotes.map((n) => String(n.id)));
11525
+ const before = fresh.notes.length;
11526
+ fresh.notes = fresh.notes.filter((n) => !drop.has(String(n.id)));
11527
+ const covered = before - fresh.notes.length;
11528
+ fresh.summary = {
11529
+ text,
11530
+ at: Date.now(),
11531
+ covered: (Number(fresh.summary && fresh.summary.covered) || 0) + covered,
11532
+ };
11533
+ finLearnSave();
11534
+ financeLog(`[öğrenme] ${sym} özetlendi: ${covered} eski ders → kalıcı özet (${text.length} karakter)`);
11535
+ return { ok: true, symbol: sym, covered, remaining: fresh.notes.length, summary: text };
11536
+ } finally {
11537
+ finLearnCompacting.delete(sym);
11538
+ }
11539
+ }
11540
+
11541
+ /* Arka plan tetikleyici: ajanı BEKLETMEDEN özetler (notlar birikince) */
11542
+ function finLearnCompactAuto(sym) {
11543
+ try { finLearnCompactSymbol(sym, false).catch(() => {}); } catch {}
11544
+ }
11545
+
11323
11546
  function finLearnSym(symbol) {
11324
11547
  const sym = String(symbol || '').trim().toUpperCase();
11325
11548
  if (!sym) return null;
@@ -11367,6 +11590,7 @@ function finLearnRecordClose({ symbol, side, net, mfe, mae, reason }) {
11367
11590
  reason: String(reason || '').slice(0, 40),
11368
11591
  });
11369
11592
  while (e.trades.length > FIN_LEARN_MAX_TRADES) e.trades.shift();
11593
+ finLearnTidy(finLearnLoad(), symbol);
11370
11594
  finLearnSave();
11371
11595
  } catch {}
11372
11596
  }
@@ -11389,6 +11613,11 @@ function finBuildLearnDigest(symbols) {
11389
11613
  if (st.lastAt) parts.push(`son: ${st.lastNet >= 0 ? '+' : ''}${st.lastNet}`);
11390
11614
  L.push(`- ${sym}: ${parts.join(' · ')}`);
11391
11615
  }
11616
+ /* KALICI ÖZET (compaction): eski derslerin sıkıştırılmış hâli — ham ders
11617
+ sayısı azalsa da birikmiş bilgi prompta girer */
11618
+ if (e.summary && e.summary.text) {
11619
+ L.push(` kalıcı özet: "${String(e.summary.text).slice(0, 500)}"`);
11620
+ }
11392
11621
  if (e.notes.length) {
11393
11622
  const last = e.notes[e.notes.length - 1];
11394
11623
  const extra = e.notes.length > 1 ? ` (+${e.notes.length - 1} ders)` : '';
@@ -11406,7 +11635,7 @@ function finLearnApi() {
11406
11635
  const sym = norm(symbol);
11407
11636
  if (sym) {
11408
11637
  const e = learn.symbols[sym];
11409
- if (!e) return { ok: true, symbol: sym, count: 0, notes: [], trades: [], stats: finLearnEmptyStats() };
11638
+ if (!e) return { ok: true, symbol: sym, count: 0, notes: [], trades: [], stats: finLearnEmptyStats(), summary: null };
11410
11639
  return {
11411
11640
  ok: true,
11412
11641
  symbol: sym,
@@ -11414,6 +11643,7 @@ function finLearnApi() {
11414
11643
  notes: e.notes.slice(-25),
11415
11644
  trades: e.trades.slice(-12),
11416
11645
  stats: { ...finLearnEmptyStats(), ...(e.stats || {}) },
11646
+ summary: e.summary && e.summary.text ? e.summary : null,
11417
11647
  };
11418
11648
  }
11419
11649
  const rows = Object.entries(learn.symbols)
@@ -11422,9 +11652,10 @@ function finLearnApi() {
11422
11652
  notes: e.notes.length,
11423
11653
  stats: { ...finLearnEmptyStats(), ...(e.stats || {}) },
11424
11654
  lastNote: String(((e.notes[e.notes.length - 1] || {}).text) || ''),
11655
+ hasSummary: !!(e.summary && e.summary.text),
11425
11656
  }))
11426
11657
  .sort((a, b) => (b.stats.lastAt || 0) - (a.stats.lastAt || 0));
11427
- return { ok: true, count: rows.length, symbols: rows.slice(0, 40) };
11658
+ return { ok: true, count: rows.length, symbols: rows.slice(0, FIN_LEARN_MAX_SYMBOLS) };
11428
11659
  },
11429
11660
  stats: ({ symbol } = {}) => {
11430
11661
  const learn = finLearnLoad();
@@ -11438,13 +11669,27 @@ function finLearnApi() {
11438
11669
  const sym = norm(symbol);
11439
11670
  const t = String(text || '').replace(/\s+/g, ' ').trim();
11440
11671
  if (!sym || !t) return { ok: false, error: 'symbol ve text gerekli' };
11672
+ const learn = finLearnLoad();
11441
11673
  const e = finLearnSym(sym);
11442
11674
  if (!e) return { ok: false, error: 'sembol yok' };
11443
- /* aynı ders tekrar yazılmasın (son 20 kayıtta aynı metin) */
11444
- const key = t.toLowerCase().slice(0, 120);
11445
- if (e.notes.slice(-20).some((n) => String(n.text || '').toLowerCase().slice(0, 120) === key)) {
11675
+ /* aynı ders tekrar yazılmasın (son 40 kayıtta normalize metin) — kopyala/
11676
+ yapıştır ve noktalama oyunlarıyla depo şişirilemez */
11677
+ const key = finLearnNoteKey(t);
11678
+ if (key && e.notes.slice(-40).some((n) => finLearnNoteKey(n.text) === key)) {
11446
11679
  return { ok: true, duplicate: true, symbol: sym, count: e.notes.length };
11447
11680
  }
11681
+ /* GÜNLÜK DERS LİMİTİ: ajan aynı gün aynı sembole ders yağdıramaz —
11682
+ hafıza öğrenmeyle zehirlenmesin, özet son dersleri temsil etsin */
11683
+ const dayAgo = Date.now() - 24 * 60 * 60 * 1000;
11684
+ const today = e.notes.filter((n) => Number(n.at) >= dayAgo).length;
11685
+ if (today >= FIN_LEARN_MAX_NOTES_DAY) {
11686
+ return {
11687
+ ok: false,
11688
+ error: `günlük ders limiti doldu (${FIN_LEARN_MAX_NOTES_DAY}/24s) — ders kaydedilmedi`,
11689
+ symbol: sym,
11690
+ count: e.notes.length,
11691
+ };
11692
+ }
11448
11693
  const note = {
11449
11694
  id: 'l' + Date.now().toString(36) + Math.random().toString(36).slice(2, 6),
11450
11695
  at: Date.now(),
@@ -11455,7 +11700,11 @@ function finLearnApi() {
11455
11700
  };
11456
11701
  e.notes.push(note);
11457
11702
  while (e.notes.length > FIN_LEARN_MAX_NOTES) e.notes.shift();
11703
+ finLearnTidy(learn, sym);
11458
11704
  finLearnSave();
11705
+ /* OPENCODE TARZI COMPACTION: notlar birikince ESKİLERİ arka planda
11706
+ özetle — ajanı bekletmez, hafıza kendiliğinden sadeleşir */
11707
+ if (e.notes.length > FIN_LEARN_COMPACT_AT) finLearnCompactAuto(sym);
11459
11708
  return { ok: true, id: note.id, symbol: sym, count: e.notes.length };
11460
11709
  },
11461
11710
  remove: ({ id, symbol } = {}) => {
@@ -11497,6 +11746,31 @@ function finLearnApi() {
11497
11746
  if (removed) finLearnSave();
11498
11747
  return { ok: true, removed };
11499
11748
  },
11749
+ drop: ({ symbol } = {}) => {
11750
+ /* sembolün TÜM kaydını (istatistik + dersler + işlemler) bırak —
11751
+ artık işlem yapılmayan/eski semboller depoda yer kaplamasın */
11752
+ const learn = finLearnLoad();
11753
+ const sym = norm(symbol);
11754
+ if (!sym || !learn.symbols[sym]) return { ok: false, error: 'sembol yok' };
11755
+ delete learn.symbols[sym];
11756
+ finLearnSave();
11757
+ return { ok: true, symbol: sym };
11758
+ },
11759
+ forget: () => {
11760
+ /* ELLE SADELEŞTİRME: TTL'i geçen ders/işlemleri ve hareketsiz sembolleri
11761
+ hemen unut (normalde her yazmada otomatik çalışır) */
11762
+ const learn = finLearnLoad();
11763
+ const r = finLearnForget(learn);
11764
+ if (r.notes || r.trades || r.symbols) finLearnSave();
11765
+ return { ok: true, ...r };
11766
+ },
11767
+ compact: ({ symbol } = {}) => {
11768
+ /* OPENCODE TARZI ÖZETLEME (elle): eski dersleri modele özetlet,
11769
+ ham yalnız son dersler kalsın */
11770
+ const sym = norm(symbol);
11771
+ if (!sym) return Promise.resolve({ ok: false, error: 'compact için symbol gerekli' });
11772
+ return finLearnCompactSymbol(sym, true);
11773
+ },
11500
11774
  };
11501
11775
  }
11502
11776
 
@@ -12397,6 +12671,26 @@ ipcMain.handle('finance:alerts:remove', async (_e, id) => {
12397
12671
  return { ok };
12398
12672
  });
12399
12673
 
12674
+ /* MT5 ÖĞRENME HAFIZASI (panel 🧠): sembol bazlı istatistik + ajan derslerini
12675
+ görüntüle/sil — depo tavanları (sembol/ders/işlem/günlük ders) çekirdekte uygulanır */
12676
+ ipcMain.handle('finance:learn', async (_e, payload) => {
12677
+ const p = payload && typeof payload === 'object' ? payload : {};
12678
+ const api = finLearnApi();
12679
+ const action = String(p.action || 'list');
12680
+ try {
12681
+ if (action === 'list') return api.list({ symbol: p.symbol });
12682
+ if (action === 'stats') return api.stats({ symbol: p.symbol });
12683
+ if (action === 'remove') return api.remove({ id: p.id, symbol: p.symbol });
12684
+ if (action === 'clear') return api.clear({ ids: p.ids, symbol: p.symbol, all: p.all });
12685
+ if (action === 'drop') return api.drop({ symbol: p.symbol });
12686
+ if (action === 'forget') return api.forget();
12687
+ if (action === 'compact') return await api.compact({ symbol: p.symbol });
12688
+ return { ok: false, error: 'bilinmeyen action' };
12689
+ } catch (e) {
12690
+ return { ok: false, error: String((e && e.message) || e).slice(0, 160) };
12691
+ }
12692
+ });
12693
+
12400
12694
  ipcMain.handle('finance:mode', async (_e, payload) => {
12401
12695
  financeState.mode = !!(payload && payload.on);
12402
12696
  /* SAAT DENETİMİ: pencere kapanınca ajanlar otomatik durur, açılınca geri başlar */
package/src/preload.js CHANGED
@@ -131,6 +131,7 @@ contextBridge.exposeInMainWorld('beast', {
131
131
  financeMt5Setup: () => ipcRenderer.invoke('finance:mt5:setup'),
132
132
  financeSymbolsList: (filter) => ipcRenderer.invoke('finance:symbols:list', filter),
133
133
  financeReport: () => ipcRenderer.invoke('finance:report'),
134
+ financeLearn: (payload) => ipcRenderer.invoke('finance:learn', payload),
134
135
  clipRead: () => ipcRenderer.invoke('clip:read'),
135
136
  clipWrite: (t) => ipcRenderer.invoke('clip:write', t),
136
137
  thinkSet: (v) => ipcRenderer.invoke('think:set', v),
@@ -427,6 +427,7 @@
427
427
  tip_bb_mobile: 'Mobil önizleme — telefon siluetinde canlı uygulama (Expo/Metro dev server)',
428
428
  tip_bb_fwd: 'İleri',
429
429
  tip_bb_reload: 'Yenile',
430
+ tip_bb_restart: 'Yeniden başlat — takılan sayfayı sıfırdan açar (oturum korunur)',
430
431
  tip_bb_shot: 'Ekran görüntüsünü sohbete ekle',
431
432
  tip_bb_open: 'Tarayıcıda aç',
432
433
  tip_bb_close: 'Kapat',
@@ -1250,6 +1251,7 @@
1250
1251
  tip_bb_mobile: 'Mobile preview — live app in a phone frame (Expo/Metro dev server)',
1251
1252
  tip_bb_fwd: 'Forward',
1252
1253
  tip_bb_reload: 'Reload',
1254
+ tip_bb_restart: 'Restart — reopens the stuck page from scratch (session kept)',
1253
1255
  tip_bb_shot: 'Add screenshot to chat',
1254
1256
  tip_bb_open: 'Open in browser',
1255
1257
  tip_bb_close: 'Close',
@@ -164,7 +164,7 @@
164
164
  </div>
165
165
  </div>
166
166
  <div class="fin-card fin-card-trader" data-fin-card="trader">
167
- <div class="fin-card-title">TRADE AJANI <span id="finTraderDot" class="fin-dot off" title="Trader durumu"></span><span class="file-spacer"></span><button id="finSetupBtn" title="MT5 kurulumunu uygula — BeastFinance.mq5 derle + AutoTrading izni + grafiğe ekle">&#x1F6E0;</button><button id="finModelRefreshBtn" title="Modelleri yeniden çek — eksik/güncel modelleri tazeler">&#x27F3;</button><button id="finSkillsBtn" title="Rol → skill eşleştirmesi — rol başına tek skill (çöp ikonuyla kaldırılır; boş = ajan kendi seçer)">&#9881;</button><button id="finAdvToggle" type="button" aria-expanded="false" title="Gelişmiş — disiplin kuralları + shadow mod"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px"><path d="M4 6h9"/><circle cx="16.5" cy="6" r="2.5"/><path d="M20 6h0.01"/><path d="M4 12h3"/><circle cx="10.5" cy="12" r="2.5"/><path d="M15 12h5"/><path d="M4 18h11"/><circle cx="18.5" cy="18" r="2.5"/></svg></button></div>
167
+ <div class="fin-card-title">TRADE AJANI <span id="finTraderDot" class="fin-dot off" title="Trader durumu"></span><span class="file-spacer"></span><button id="finSetupBtn" title="MT5 kurulumunu uygula — BeastFinance.mq5 derle + AutoTrading izni + grafiğe ekle">&#x1F6E0;</button><button id="finModelRefreshBtn" title="Modelleri yeniden çek — eksik/güncel modelleri tazeler">&#x27F3;</button><button id="finLearnBtn" title="MT5 öğrenme hafızası — neler öğrendi: sembol bazlı dersler + otomatik istatistik (görüntüle/sil)">&#x1F9E0;</button><button id="finSkillsBtn" title="Rol → skill eşleştirmesi — rol başına tek skill (çöp ikonuyla kaldırılır; boş = ajan kendi seçer)">&#9881;</button><button id="finAdvToggle" type="button" aria-expanded="false" title="Gelişmiş — disiplin kuralları + shadow mod"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px"><path d="M4 6h9"/><circle cx="16.5" cy="6" r="2.5"/><path d="M20 6h0.01"/><path d="M4 12h3"/><circle cx="10.5" cy="12" r="2.5"/><path d="M15 12h5"/><path d="M4 18h11"/><circle cx="18.5" cy="18" r="2.5"/></svg></button></div>
168
168
  <div class="fin-field">
169
169
  <span>Model</span>
170
170
  <select id="finTraderModel" title="Trader ajanının modeli — farklı API/model seçilebilir"></select>
@@ -858,6 +858,21 @@
858
858
  </div>
859
859
  </div>
860
860
 
861
+ <div id="finLearnOverlay" class="mini-overlay" hidden>
862
+ <div class="mini-dialog fin-learn-dialog">
863
+ <div class="mini-head">
864
+ <span class="mini-title">MT5 ÖĞRENME HAFIZASI</span>
865
+ <span id="finLearnCount" class="fin-sym-count"></span>
866
+ <button id="finLearnClear" title="TÜM sembollerin derslerini sil — otomatik istatistikler korunur (depo şişmesi/zehirlenmesi önlenir)">Tüm dersleri sil</button>
867
+ <button class="mini-close" id="finLearnClose" title="Kapat">&#x00D7;</button>
868
+ </div>
869
+ <div class="mini-body">
870
+ <div id="finLearnList" class="mini-list"></div>
871
+ <div class="fin-sym-hint">Trade ajanının çekirdekteki sembol bazlı hafızası: her işlem kapanışı otomatik istatistiğe işlenir, ajan dersleri mt5_ogrenme ile yazar. Sembol satırına tıkla → kalıcı özeti, dersleri ve son işlemleri gör; ÖZET ile eski dersleri modele sıkıştırt (24+ ders birikince otomatik), tek dersi çöp ikonuyla sil, sembol kaydını tümden bırak. Hafıza sürekli sadeleşir: 30 günden eski dersler, 90 günden eski işlem kayıtları ve 120 gün hareketsiz semboller otomatik unutulur.</div>
872
+ </div>
873
+ </div>
874
+ </div>
875
+
861
876
  <div id="finTeamOverlay" class="mini-overlay" hidden>
862
877
  <div class="mini-dialog fin-sym-dialog">
863
878
  <div class="mini-head">
@@ -942,6 +957,7 @@
942
957
  <button id="bbBack" title="Geri" data-i18n-title="tip_bb_back">&#x2190;</button>
943
958
  <button id="bbFwd" title="İleri" data-i18n-title="tip_bb_fwd">&#x2192;</button>
944
959
  <button id="bbReload" title="Yenile" data-i18n-title="tip_bb_reload">&#x21BB;</button>
960
+ <button id="bbRestart" title="Yeniden başlat — takılan sayfayı sıfırdan açar (oturum korunur)" data-i18n-title="tip_bb_restart">&#x27F3;</button>
945
961
  <input id="bbUrl" placeholder="adres yaz veya ara…" autocomplete="off" spellcheck="false" data-i18n-ph="bb_ph" />
946
962
  <button id="bbPhone" title="Telefon modu — mobil versiyon / web versiyonu" data-i18n-title="tip_bb_phone"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="7" y="2" width="10" height="20" rx="2.5"/><line x1="11" y1="18" x2="13" y2="18"/></svg></button>
947
963
  <button id="bbOpenExt" title="Tarayıcıda aç" data-i18n-title="tip_bb_open">&#x2197;</button>
@@ -92,6 +92,7 @@ const els = {
92
92
  bbBack: $('#bbBack'),
93
93
  bbFwd: $('#bbFwd'),
94
94
  bbReload: $('#bbReload'),
95
+ bbRestart: $('#bbRestart'),
95
96
  bbUrl: $('#bbUrl'),
96
97
  bbOpenExt: $('#bbOpenExt'),
97
98
  bbClose: $('#bbClose'),
@@ -260,6 +261,12 @@ const els = {
260
261
  finSkillsOverlay: $('#finSkillsOverlay'),
261
262
  finSkillsClose: $('#finSkillsClose'),
262
263
  finSkillsList: $('#finSkillsList'),
264
+ finLearnBtn: $('#finLearnBtn'),
265
+ finLearnOverlay: $('#finLearnOverlay'),
266
+ finLearnClose: $('#finLearnClose'),
267
+ finLearnList: $('#finLearnList'),
268
+ finLearnCount: $('#finLearnCount'),
269
+ finLearnClear: $('#finLearnClear'),
263
270
  finSymSearch: $('#finSymSearch'),
264
271
  finSymPick: $('#finSymPick'),
265
272
  finSymCount: $('#finSymCount'),
@@ -7813,6 +7820,16 @@ async function init() {
7813
7820
  els.bbBack.addEventListener('click', () => beast.browserCtrl('back'));
7814
7821
  els.bbFwd.addEventListener('click', () => beast.browserCtrl('forward'));
7815
7822
  els.bbReload.addEventListener('click', () => beast.browserCtrl('reload'));
7823
+ if (els.bbRestart) {
7824
+ els.bbRestart.addEventListener('click', async () => {
7825
+ try {
7826
+ const r = await beast.browserCtrl('restart');
7827
+ toast(r && r.ok ? 'Tarayıcı yeniden başlatıldı' : 'Tarayıcı yeniden başlatılamadı');
7828
+ } catch (e) {
7829
+ toast('Tarayıcı yeniden başlatılamadı');
7830
+ }
7831
+ });
7832
+ }
7816
7833
  if (els.bbPhone) {
7817
7834
  els.bbPhone.addEventListener('click', () => {
7818
7835
  /* web ↔ mobil: aynı sitenin iki versiyonu arasındaki anahtar */
@@ -11030,6 +11047,303 @@ if (els.finSkillsOverlay) {
11030
11047
  });
11031
11048
  }
11032
11049
 
11050
+ /* ---------- MT5 ÖĞRENME HAFIZASI modalı (TRADE AJANI 🧠) ----------
11051
+ Çekirdekteki (main process) sembol bazlı öğrenme deposunu gösterir:
11052
+ otomatik istatistik + ajanın mt5_ogrenme ile yazdığı dersler. Tek ders
11053
+ silinebilir, sembol kaydı tümden bırakılabilir; depo tavanları
11054
+ (60 sembol / 120 ders / 60 işlem / günlük 12 ders) çekirdekte uygulanır. */
11055
+ const FIN_LEARN_TRASH_SVG =
11056
+ '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px"><path d="M3 6h18"/><path d="M8 6V4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v2"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/></svg>';
11057
+
11058
+ let finLearnRows = [];
11059
+ let finLearnOpenSym = '';
11060
+ let finLearnBusy = false;
11061
+
11062
+ function finLearnCall(payload) {
11063
+ return beast.financeLearn(payload).catch(() => ({ ok: false }));
11064
+ }
11065
+
11066
+ function finLearnSign(v) {
11067
+ const n = Math.round((Number(v) || 0) * 100) / 100;
11068
+ return (n >= 0 ? '+' : '') + n;
11069
+ }
11070
+
11071
+ function finLearnTime(ts) {
11072
+ const t = Number(ts) || 0;
11073
+ if (!t) return '';
11074
+ const d = new Date(t);
11075
+ const p = (x) => String(x).padStart(2, '0');
11076
+ return p(d.getDate()) + '.' + p(d.getMonth() + 1) + ' ' + p(d.getHours()) + ':' + p(d.getMinutes());
11077
+ }
11078
+
11079
+ function finLearnKindLabel(kind) {
11080
+ return ({ pattern: 'desen', mistake: 'hata', rule: 'kural', observation: 'gözlem' })[String(kind || '')] || 'gözlem';
11081
+ }
11082
+
11083
+ function finLearnStatsText(st) {
11084
+ const trades = Number(st && st.trades) || 0;
11085
+ if (!trades) return '';
11086
+ const parts = [
11087
+ trades + ' işlem',
11088
+ '%' + Math.round(((Number(st.wins) || 0) / trades) * 100) + ' kazanç',
11089
+ 'net ' + finLearnSign(st.net),
11090
+ ];
11091
+ if (Number(st.streak) >= 2) parts.push(Number(st.streak) + ' ardışık kayıp');
11092
+ if (Number(st.givebacks) > 0) parts.push(Number(st.givebacks) + ' kez kâr geri verildi');
11093
+ if (st.lastAt) parts.push('son ' + finLearnSign(st.lastNet));
11094
+ return parts.join(' · ');
11095
+ }
11096
+
11097
+ async function finLearnOpen() {
11098
+ if (!els.finLearnOverlay) return;
11099
+ finLearnOpenSym = '';
11100
+ els.finLearnOverlay.hidden = false;
11101
+ await finLearnRefresh();
11102
+ }
11103
+
11104
+ function finLearnCloseModal() {
11105
+ if (els.finLearnOverlay) els.finLearnOverlay.hidden = true;
11106
+ }
11107
+
11108
+ async function finLearnRefresh() {
11109
+ const box = els.finLearnList;
11110
+ if (!box || finLearnBusy) return;
11111
+ finLearnBusy = true;
11112
+ box.innerHTML = '<div class="fin-empty">Öğrenme kayıtları yükleniyor…</div>';
11113
+ const r = await finLearnCall({ action: 'list' });
11114
+ finLearnBusy = false;
11115
+ finLearnRows = (r && Array.isArray(r.symbols)) ? r.symbols : [];
11116
+ finLearnRender();
11117
+ }
11118
+
11119
+ function finLearnRender() {
11120
+ const box = els.finLearnList;
11121
+ if (!box) return;
11122
+ box.textContent = '';
11123
+ if (els.finLearnCount) els.finLearnCount.textContent = finLearnRows.length ? finLearnRows.length + ' sembol' : '';
11124
+ if (!finLearnRows.length) {
11125
+ box.innerHTML = '<div class="fin-empty">Henüz öğrenilmiş bir şey yok — işlem kapandıkça istatistik, ajan ders yazdıkça kayıt burada birikir.</div>';
11126
+ return;
11127
+ }
11128
+ for (const row of finLearnRows) box.appendChild(finLearnSymEl(row));
11129
+ /* silme sonrası yeniden çizimde açık sembol detayı açık kalsın */
11130
+ if (finLearnOpenSym) {
11131
+ const el = [...box.children].find((c) => c.dataset && c.dataset.sym === finLearnOpenSym);
11132
+ if (el && el._finToggle) el._finToggle();
11133
+ }
11134
+ }
11135
+
11136
+ function finLearnSymEl(row) {
11137
+ const sym = String(row.symbol || '');
11138
+ const el = document.createElement('div');
11139
+ el.className = 'fin-learn-sym';
11140
+ el.dataset.sym = sym;
11141
+ const head = document.createElement('div');
11142
+ head.className = 'fin-learn-sym-head';
11143
+ const name = document.createElement('b');
11144
+ name.textContent = sym;
11145
+ const stats = document.createElement('span');
11146
+ stats.className = 'fin-learn-sym-stats';
11147
+ stats.textContent = finLearnStatsText(row.stats) || 'henüz kapanan işlem yok';
11148
+ const noteCount = document.createElement('span');
11149
+ noteCount.className = 'fin-learn-sym-notes';
11150
+ noteCount.textContent = (Number(row.notes) || 0) + ' ders' + (row.hasSummary ? ' · özet' : '');
11151
+ /* ÖZETLE (opencode tarzı compaction): eski dersler modele özetlettirilir */
11152
+ const sum = document.createElement('button');
11153
+ sum.type = 'button';
11154
+ sum.className = 'fin-learn-sum-btn';
11155
+ sum.title = 'Eski dersleri modele özetlet — hafıza sadeleşir (24+ ders birikince otomatik de çalışır)';
11156
+ sum.textContent = 'ÖZET';
11157
+ sum.addEventListener('click', async (e) => {
11158
+ e.stopPropagation();
11159
+ sum.disabled = true;
11160
+ sum.textContent = '…';
11161
+ const r = await finLearnCall({ action: 'compact', symbol: sym });
11162
+ sum.disabled = false;
11163
+ sum.textContent = 'ÖZET';
11164
+ if (r && r.ok) {
11165
+ toast(sym + ' özetlendi: ' + (Number(r.covered) || 0) + ' eski ders kalıcı özete sıkıştı');
11166
+ await finLearnRefresh();
11167
+ } else {
11168
+ toast('Özetlenemedi: ' + ((r && r.error) || '?'));
11169
+ }
11170
+ });
11171
+ const drop = document.createElement('button');
11172
+ drop.type = 'button';
11173
+ drop.className = 'fin-learn-drop';
11174
+ drop.title = sym + ' kaydını TÜMDEN sil (dersler + otomatik istatistik)';
11175
+ drop.innerHTML = FIN_LEARN_TRASH_SVG;
11176
+ drop.addEventListener('click', async (e) => {
11177
+ e.stopPropagation();
11178
+ const ok = await uiConfirm(sym + ' için TÜM öğrenme kaydı (dersler + istatistik) silinsin mi?', 'Sil', 'Vazgeç');
11179
+ if (!ok) return;
11180
+ await finLearnCall({ action: 'drop', symbol: sym });
11181
+ if (finLearnOpenSym === sym) finLearnOpenSym = '';
11182
+ toast(sym + ' öğrenme kaydı silindi');
11183
+ await finLearnRefresh();
11184
+ });
11185
+ head.appendChild(name);
11186
+ head.appendChild(stats);
11187
+ head.appendChild(noteCount);
11188
+ head.appendChild(sum);
11189
+ head.appendChild(drop);
11190
+ el.appendChild(head);
11191
+
11192
+ const body = document.createElement('div');
11193
+ body.className = 'fin-learn-body';
11194
+ body.hidden = true;
11195
+ el.appendChild(body);
11196
+ let loaded = false;
11197
+ let busy = false;
11198
+ el._finToggle = async () => {
11199
+ if (busy) return;
11200
+ if (loaded && !body.hidden) {
11201
+ body.hidden = true;
11202
+ if (finLearnOpenSym === sym) finLearnOpenSym = '';
11203
+ return;
11204
+ }
11205
+ if (loaded) {
11206
+ body.hidden = false;
11207
+ finLearnOpenSym = sym;
11208
+ return;
11209
+ }
11210
+ busy = true;
11211
+ body.hidden = false;
11212
+ finLearnOpenSym = sym;
11213
+ body.innerHTML = '<div class="fin-empty">yükleniyor…</div>';
11214
+ const d = await finLearnCall({ action: 'list', symbol: sym });
11215
+ if (!els.finLearnOverlay || els.finLearnOverlay.hidden) { busy = false; return; }
11216
+ finLearnDetail(body, sym, d);
11217
+ loaded = true;
11218
+ busy = false;
11219
+ };
11220
+ head.addEventListener('click', () => { el._finToggle(); });
11221
+ return el;
11222
+ }
11223
+
11224
+ function finLearnDetail(body, sym, d) {
11225
+ body.textContent = '';
11226
+ const st = (d && d.stats) || {};
11227
+ const info = document.createElement('div');
11228
+ info.className = 'fin-learn-detail-stats';
11229
+ const trades = Number(st.trades) || 0;
11230
+ const bits = [];
11231
+ if (trades) {
11232
+ bits.push(`${trades} işlem · ${Number(st.wins) || 0} kazanç · ${Number(st.losses) || 0} kayıp`);
11233
+ bits.push(`net ${finLearnSign(st.net)} · en iyi ${finLearnSign(st.bestNet)} · en kötü ${finLearnSign(st.worstNet)}`);
11234
+ bits.push(`MFE toplam ${finLearnSign(st.mfe)} · MAE toplam ${finLearnSign(st.mae)}`);
11235
+ if (Number(st.givebacks) > 0) bits.push(`${Number(st.givebacks)} kez kâr geri verildi (${finLearnSign(st.givebackAmount)})`);
11236
+ } else {
11237
+ bits.push('Bu sembolde henüz kapanan işlem yok — istatistik ilk kapanışta oluşur.');
11238
+ }
11239
+ info.textContent = bits.join(' — ');
11240
+ body.appendChild(info);
11241
+
11242
+ /* KALICI ÖZET (opencode tarzı compaction): eski derslerin modele
11243
+ özetlettirilmiş hâli — ham dersler silinse de bilgi burada kalır */
11244
+ const summ = d && d.summary && d.summary.text ? d.summary : null;
11245
+ if (summ) {
11246
+ const sHead = document.createElement('div');
11247
+ sHead.className = 'fin-learn-sec';
11248
+ sHead.textContent = 'KALICI ÖZET (opencode tarzı sıkıştırma' +
11249
+ (Number(summ.covered) ? ' · ' + Number(summ.covered) + ' eski ders' : '') + ')';
11250
+ body.appendChild(sHead);
11251
+ const sBox = document.createElement('div');
11252
+ sBox.className = 'fin-learn-summary';
11253
+ sBox.textContent = String(summ.text || '');
11254
+ body.appendChild(sBox);
11255
+ const sAt = document.createElement('div');
11256
+ sAt.className = 'fin-learn-summary-at';
11257
+ sAt.textContent = 'güncelleme: ' + finLearnTime(summ.at);
11258
+ body.appendChild(sAt);
11259
+ }
11260
+
11261
+ const notes = (d && Array.isArray(d.notes)) ? d.notes : [];
11262
+ const nHead = document.createElement('div');
11263
+ nHead.className = 'fin-learn-sec';
11264
+ nHead.textContent = 'DERSLER (' + notes.length + ')';
11265
+ body.appendChild(nHead);
11266
+ if (!notes.length) {
11267
+ const e = document.createElement('div');
11268
+ e.className = 'fin-empty';
11269
+ e.textContent = 'Ders yok — ajan her kapanıştan sonra mt5_ogrenme ile buraya yazar.';
11270
+ body.appendChild(e);
11271
+ } else {
11272
+ for (const n of notes.slice().reverse()) {
11273
+ const rowEl = document.createElement('div');
11274
+ rowEl.className = 'fin-learn-note';
11275
+ const kind = document.createElement('span');
11276
+ kind.className = 'fin-learn-kind k-' + String(n.kind || 'observation');
11277
+ kind.textContent = finLearnKindLabel(n.kind);
11278
+ const txt = document.createElement('span');
11279
+ txt.className = 'fin-learn-note-text';
11280
+ txt.textContent = String(n.text || '');
11281
+ txt.title = txt.textContent;
11282
+ const at = document.createElement('span');
11283
+ at.className = 'fin-learn-note-at';
11284
+ at.textContent = finLearnTime(n.at);
11285
+ const del = document.createElement('button');
11286
+ del.type = 'button';
11287
+ del.className = 'fin-learn-note-del';
11288
+ del.title = 'Bu dersi sil — yanlış/geçersiz ders hafızayı zehirlemesin';
11289
+ del.innerHTML = FIN_LEARN_TRASH_SVG;
11290
+ del.addEventListener('click', async () => {
11291
+ const r = await finLearnCall({ action: 'remove', id: n.id, symbol: sym });
11292
+ if (r && r.ok) {
11293
+ toast('Ders silindi');
11294
+ await finLearnRefresh();
11295
+ } else {
11296
+ toast('Ders silinemedi');
11297
+ }
11298
+ });
11299
+ rowEl.appendChild(kind);
11300
+ rowEl.appendChild(txt);
11301
+ rowEl.appendChild(at);
11302
+ rowEl.appendChild(del);
11303
+ body.appendChild(rowEl);
11304
+ }
11305
+ }
11306
+
11307
+ const tradeRows = (d && Array.isArray(d.trades)) ? d.trades : [];
11308
+ if (tradeRows.length) {
11309
+ const tHead = document.createElement('div');
11310
+ tHead.className = 'fin-learn-sec';
11311
+ tHead.textContent = 'SON İŞLEMLER (otomatik)';
11312
+ body.appendChild(tHead);
11313
+ for (const t of tradeRows.slice().reverse()) {
11314
+ const tr = document.createElement('div');
11315
+ tr.className = 'fin-learn-trade ' + (Number(t.net) < 0 ? 'loss' : 'win');
11316
+ const side = String(t.side || '').toLowerCase();
11317
+ const sideTxt = side === 'buy' ? 'AL' : side === 'sell' ? 'SAT' : (side || '?');
11318
+ tr.textContent = `${finLearnTime(t.at)} · ${sideTxt} · ${finLearnSign(t.net)}` +
11319
+ (t.mfe != null ? ` · MFE ${finLearnSign(t.mfe)}` : '') +
11320
+ (t.mae != null ? ` · MAE ${finLearnSign(t.mae)}` : '') +
11321
+ (t.reason ? ` · ${String(t.reason)}` : '');
11322
+ body.appendChild(tr);
11323
+ }
11324
+ }
11325
+ }
11326
+
11327
+ if (els.finLearnBtn) els.finLearnBtn.addEventListener('click', () => { finLearnOpen(); });
11328
+ if (els.finLearnClose) els.finLearnClose.addEventListener('click', finLearnCloseModal);
11329
+ if (els.finLearnOverlay) {
11330
+ els.finLearnOverlay.addEventListener('click', (e) => {
11331
+ if (e.target === els.finLearnOverlay) finLearnCloseModal();
11332
+ });
11333
+ els.finLearnOverlay.addEventListener('keydown', (e) => {
11334
+ if (e.key === 'Escape') { e.stopPropagation(); finLearnCloseModal(); }
11335
+ });
11336
+ }
11337
+ if (els.finLearnClear) {
11338
+ els.finLearnClear.addEventListener('click', async () => {
11339
+ const ok = await uiConfirm('TÜM sembollerin öğrenme DERSLERİ silinsin mi? Otomatik istatistikler korunur; işlem geri alınamaz.', 'Tümünü sil', 'Vazgeç');
11340
+ if (!ok) return;
11341
+ const r = await finLearnCall({ action: 'clear', all: true });
11342
+ toast(r && r.ok ? ('Dersler temizlendi' + (r.removed ? ' (' + r.removed + ' kayıt)' : '')) : 'Dersler temizlenemedi');
11343
+ await finLearnRefresh();
11344
+ });
11345
+ }
11346
+
11033
11347
  if (els.finSymAdd) els.finSymAdd.addEventListener('click', finSymPickerOpen);
11034
11348
  if (els.finWatchClear) {
11035
11349
  els.finWatchClear.addEventListener('click', async () => {
@@ -5843,6 +5843,121 @@ body.finance-mode #composerWrap { margin-right: var(--finW, 400px); }
5843
5843
  .fin-skill-chip.on { border-color: var(--accent); color: var(--accent); background: var(--accent-dim); }
5844
5844
  .fin-skill-chip input { flex: none; width: auto; margin: 0; }
5845
5845
 
5846
+ /* MT5 ÖĞRENME HAFIZASI modalı (TRADE AJANI 🧠) */
5847
+ .fin-learn-dialog { width: 580px; max-width: calc(100vw - 40px); }
5848
+ .fin-learn-dialog .mini-body {
5849
+ flex: 1;
5850
+ min-height: 0;
5851
+ display: flex;
5852
+ flex-direction: column;
5853
+ overflow: hidden;
5854
+ }
5855
+ #finLearnList {
5856
+ flex: 1;
5857
+ min-height: 0;
5858
+ overflow-y: auto;
5859
+ display: flex;
5860
+ flex-direction: column;
5861
+ gap: 8px;
5862
+ }
5863
+ #finLearnClear {
5864
+ flex: none;
5865
+ border: 1px solid var(--border);
5866
+ background: var(--panel2);
5867
+ color: var(--muted);
5868
+ font-size: 10.5px;
5869
+ font-weight: 700;
5870
+ border-radius: 7px;
5871
+ padding: 4px 8px;
5872
+ cursor: pointer;
5873
+ margin-right: 6px;
5874
+ }
5875
+ #finLearnClear:hover { color: #ef4444; border-color: #ef4444; }
5876
+ .fin-learn-sym { border: 1px solid var(--border); border-radius: 8px; padding: 7px 9px; background: var(--panel2); }
5877
+ .fin-learn-sym-head { display: flex; align-items: center; gap: 7px; cursor: pointer; }
5878
+ .fin-learn-sym-head b { flex: none; font-size: 12px; color: var(--accent); }
5879
+ .fin-learn-sym-stats {
5880
+ flex: 1;
5881
+ min-width: 0;
5882
+ font-size: 10px;
5883
+ color: var(--muted);
5884
+ overflow: hidden;
5885
+ text-overflow: ellipsis;
5886
+ white-space: nowrap;
5887
+ }
5888
+ .fin-learn-sym-notes { flex: none; font-size: 9.5px; font-weight: 800; letter-spacing: 0.4px; color: var(--muted); }
5889
+ .fin-learn-sum-btn {
5890
+ flex: none;
5891
+ border: 1px solid var(--border);
5892
+ background: var(--panel);
5893
+ color: var(--muted);
5894
+ font-size: 9px;
5895
+ font-weight: 800;
5896
+ letter-spacing: 0.4px;
5897
+ border-radius: 5px;
5898
+ padding: 2px 6px;
5899
+ cursor: pointer;
5900
+ }
5901
+ .fin-learn-sum-btn:hover { color: var(--accent); border-color: var(--accent); }
5902
+ .fin-learn-sum-btn:disabled { opacity: 0.5; cursor: default; }
5903
+ .fin-learn-summary {
5904
+ border: 1px solid var(--border);
5905
+ border-left: 3px solid var(--accent);
5906
+ border-radius: 6px;
5907
+ background: var(--panel);
5908
+ padding: 6px 8px;
5909
+ font-size: 10.5px;
5910
+ color: var(--text);
5911
+ line-height: 1.45;
5912
+ word-break: break-word;
5913
+ }
5914
+ .fin-learn-summary-at { font-size: 9px; color: var(--muted); margin: 3px 0 2px; }
5915
+ .fin-learn-drop,
5916
+ .fin-learn-note-del {
5917
+ flex: none;
5918
+ border: none;
5919
+ background: none;
5920
+ color: var(--muted);
5921
+ line-height: 0;
5922
+ padding: 3px 4px;
5923
+ border-radius: 6px;
5924
+ cursor: pointer;
5925
+ }
5926
+ .fin-learn-drop:hover,
5927
+ .fin-learn-note-del:hover { color: #ef4444; background: var(--accent-dim); }
5928
+ .fin-learn-body { margin-top: 6px; border-top: 1px dashed var(--border); padding-top: 6px; }
5929
+ .fin-learn-detail-stats { font-size: 10px; color: var(--muted); margin-bottom: 6px; }
5930
+ .fin-learn-sec { font-size: 9.5px; font-weight: 800; letter-spacing: 0.5px; color: var(--muted); margin: 6px 0 4px; }
5931
+ .fin-learn-note {
5932
+ display: flex;
5933
+ align-items: flex-start;
5934
+ gap: 6px;
5935
+ padding: 4px 5px;
5936
+ border-radius: 6px;
5937
+ background: var(--panel);
5938
+ border: 1px solid var(--border);
5939
+ margin-bottom: 4px;
5940
+ }
5941
+ .fin-learn-kind {
5942
+ flex: none;
5943
+ font-size: 9px;
5944
+ font-weight: 800;
5945
+ letter-spacing: 0.4px;
5946
+ border-radius: 5px;
5947
+ padding: 2px 5px;
5948
+ border: 1px solid var(--border);
5949
+ color: var(--muted);
5950
+ text-transform: uppercase;
5951
+ }
5952
+ .fin-learn-kind.k-pattern { color: #22c55e; border-color: #22c55e; }
5953
+ .fin-learn-kind.k-mistake { color: #ef4444; border-color: #ef4444; }
5954
+ .fin-learn-kind.k-rule { color: #e6a117; border-color: #e6a117; }
5955
+ .fin-learn-note-text { flex: 1; min-width: 0; font-size: 10.5px; color: var(--text); word-break: break-word; }
5956
+ .fin-learn-note-at { flex: none; font-size: 9px; color: var(--muted); }
5957
+ .fin-learn-trade { font-size: 10px; color: var(--muted); padding: 2px 4px; border-radius: 5px; }
5958
+ .fin-learn-trade.win { color: #22c55e; }
5959
+ .fin-learn-trade.loss { color: #ef4444; }
5960
+
5846
5961
  /* FINANCE GÖRÜNÜM AYARLARI modalı (başlıktaki ⚙) — isteğe bağlı bölümler */
5847
5962
  .fin-card[hidden] { display: none; }
5848
5963
  .fin-settings-dialog { width: min(440px, 90vw); height: auto; max-height: min(560px, 84vh); }