beast-agent 2.63.4 → 2.63.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/preload.js CHANGED
@@ -107,6 +107,10 @@ contextBridge.exposeInMainWorld('beast', {
107
107
  /* Supermemory (lokal) bellek katmanı ayarları */
108
108
  supermemoryGet: () => ipcRenderer.invoke('supermemory:get'),
109
109
  supermemorySet: (cfg) => ipcRenderer.invoke('supermemory:set', cfg),
110
+ /* TypeSafe (System One/Jev) — typesafe_decision aracı için API anahtarı */
111
+ typesafeGet: () => ipcRenderer.invoke('typesafe:get'),
112
+ typesafeSet: (cfg) => ipcRenderer.invoke('typesafe:set', cfg),
113
+ typesafeTest: (payload) => ipcRenderer.invoke('typesafe:test', payload),
110
114
  studioSend: (msg, attachments) => ipcRenderer.invoke('studio:send', { msg, attachments }),
111
115
  studioStop: () => ipcRenderer.invoke('studio:stop'),
112
116
  studioNew: () => ipcRenderer.invoke('studio:new'),
@@ -79,6 +79,7 @@
79
79
  tab_email: 'E-posta',
80
80
  tab_integrations: 'Entegrasyonlar',
81
81
  tab_websearch: 'Web Arama',
82
+ tab_typesafe: 'TypeSafe',
82
83
  tab_mcp: 'MCP',
83
84
  mcp_h2: 'MCP Serverlar',
84
85
  mcp_sub: 'Model Context Protocol — dış araç serverları (filesystem, git, fetch, memory…). Kaynak: mcp.json (aşağıda düzenlenir). Araçlar modele mcp__server__tool adıyla açılır, riskli sayılır (onay kapısı açıkken onay ister).',
@@ -752,6 +753,24 @@
752
753
  mail_reply: 'Yanıtla',
753
754
  mail_read_fail: 'Mail okunamadı',
754
755
  mail_need_to: 'Alıcı adresi gerekli',
756
+ /* TypeSafe (System One / Jev) */
757
+ ts_h2: 'TypeSafe (System One · Jev)',
758
+ ts_sub: 'Tipli karar motoru: metin üretmeden olasılıklı yanıt (noul/choice/score). Anahtar girilince ajanlar typesafe_decision aracını kullanır; Beast Finance rol→skill eşleştirmesinde "typesafe-ai" seçilirse roller bu kararları kullanır.',
759
+ ts_key_label: 'TypeSafe API anahtarı (typesafe.ai)',
760
+ ts_model_label: 'Model (varsayılan: jev-latest)',
761
+ ts_status_set: 'TypeSafe aktif — kayıtlı anahtar: *** · model: ',
762
+ ts_status_unset: 'TypeSafe pasif — anahtar girmeden typesafe_decision aracı çalışmaz.',
763
+ ts_save: 'Kaydet',
764
+ ts_test: 'Bağlantıyı Test Et',
765
+ ts_testing: 'Test ediliyor…',
766
+ ts_clear: 'Anahtarı Sil',
767
+ ts_test_ok: 'TypeSafe bağlantısı başarılı — model: ',
768
+ ts_test_fail: 'TypeSafe testi başarısız: ',
769
+ ts_empty_toast: 'Anahtar boş — mevcut anahtar korundu',
770
+ ts_saved_toast: 'TypeSafe anahtarı kaydedildi',
771
+ ts_cleared_toast: 'TypeSafe anahtarı silindi',
772
+ ts_fail_toast: 'TypeSafe kaydedilemedi',
773
+ ts_hint: 'İpucu: Rol→Skill eşleştirmesinde "typesafe-ai" seçilen roller (ve ana trader playbook) kararlarını typesafe_decision ile alır; anahtar yoksa diğer ajanlar chat modeliyle devam eder.',
755
774
  },
756
775
  en: {
757
776
  newChat: '+ New Chat',
@@ -820,6 +839,7 @@
820
839
  tab_email: 'E-mail',
821
840
  tab_integrations: 'Integrations',
822
841
  tab_websearch: 'Web Search',
842
+ tab_typesafe: 'TypeSafe',
823
843
  tab_mcp: 'MCP',
824
844
  mcp_h2: 'MCP Servers',
825
845
  mcp_sub: 'Model Context Protocol — external tool servers (filesystem, git, fetch, memory…). Source: mcp.json (editable below). Tools are exposed to the model as mcp__server__tool and count as risky (approval gate applies when enabled).',
@@ -1488,6 +1508,24 @@
1488
1508
  store_install_fail: 'Install failed',
1489
1509
  store_like_ok: 'Like saved',
1490
1510
  store_login_note: 'Your Beast ID is anonymous; your real Beast Code is never shared.',
1511
+ /* TypeSafe (System One / Jev) */
1512
+ ts_h2: 'TypeSafe (System One · Jev)',
1513
+ ts_sub: 'Typed decision engine: probabilistic answers without text generation (noul/choice/score). Once a key is set, agents can use the typesafe_decision tool; when "typesafe-ai" is picked in Beast Finance role→skill matching, those roles use it.',
1514
+ ts_key_label: 'TypeSafe API key (typesafe.ai)',
1515
+ ts_model_label: 'Model (default: jev-latest)',
1516
+ ts_status_set: 'TypeSafe active — saved key: *** · model: ',
1517
+ ts_status_unset: 'TypeSafe inactive — typesafe_decision does nothing without a key.',
1518
+ ts_save: 'Save',
1519
+ ts_test: 'Test Connection',
1520
+ ts_testing: 'Testing…',
1521
+ ts_clear: 'Delete Key',
1522
+ ts_test_ok: 'TypeSafe connection OK — model: ',
1523
+ ts_test_fail: 'TypeSafe test failed: ',
1524
+ ts_empty_toast: 'Key is empty — existing key kept',
1525
+ ts_saved_toast: 'TypeSafe key saved',
1526
+ ts_cleared_toast: 'TypeSafe key deleted',
1527
+ ts_fail_toast: 'Could not save TypeSafe key',
1528
+ ts_hint: 'Tip: roles (and the main trader playbook) matched to "typesafe-ai" use typesafe_decision for their judgments; without a key other agents keep using the chat model.',
1491
1529
  },
1492
1530
  };
1493
1531
 
@@ -606,6 +606,7 @@
606
606
  <button class="tab" data-tab="email" data-i18n="tab_email">E-posta</button>
607
607
  <button class="tab" data-tab="integrations" data-i18n="tab_integrations">Entegrasyonlar</button>
608
608
  <button class="tab" data-tab="websearch" data-i18n="tab_websearch">Web Arama</button>
609
+ <button class="tab" data-tab="typesafe" data-i18n="tab_typesafe">TypeSafe</button>
609
610
  <button class="tab" data-tab="mcp" data-i18n="tab_mcp">MCP</button>
610
611
  <button class="tab" data-tab="events" data-i18n="tab_events">Olay Merkezi</button>
611
612
  <button class="tab" data-tab="empati" data-i18n="tab_empati">Empati Loop</button>
@@ -634,6 +635,7 @@
634
635
  <div id="tab-email" class="pane" hidden></div>
635
636
  <div id="tab-integrations" class="pane" hidden></div>
636
637
  <div id="tab-websearch" class="pane" hidden></div>
638
+ <div id="tab-typesafe" class="pane" hidden></div>
637
639
  <div id="tab-mcp" class="pane" hidden></div>
638
640
  <div id="tab-events" class="pane" hidden></div>
639
641
  <div id="tab-empati" class="pane" hidden></div>
@@ -854,7 +856,7 @@
854
856
  </div>
855
857
  <div class="mini-body">
856
858
  <div id="finSkillsList" class="mini-list"></div>
857
- <div class="fin-sym-hint">Her rol için TEK skill seçilir — aynı role birden fazla skill seçilemez. Seçili skill'i satırın sağ üstündeki çöp ikonuyla kaldırabilirsin; boş bırakırsan ajan skill'i göreve göre SKILLS kataloğundan kendi seçer. Seçim, koşan ajanlar dahil sonraki turdan itibaren sistem promptuna girer.</div>
859
+ <div class="fin-sym-hint">Her rol için EN FAZLA 2 skill seçilir (ör. price-action + typesafe-ai). Seçili skill'i satırın sağ üstündeki çöp ikonuyla kaldırabilirsin; boş bırakırsan ajan skill'i göreve göre SKILLS kataloğundan kendi seçer. Seçim, koşan ajanlar dahil sonraki turdan itibaren geçerli olur.<br><b>typesafe-ai seçilirse:</b> o ajan LLM KULLANMAZ — tur verisi yalnız TypeSafe System One'a gider (tipli kararlar), cevaplar AJAN DM'de tüm ekibe görünür ve ajanlar birbirinin yanıtını görür; emirler YALNIZ market buy/sell olarak açılır (bekleyen emir yok). Ayarlar → TypeSafe'ten API anahtarı gerekir.</div>
858
860
  </div>
859
861
  </div>
860
862
  </div>
@@ -1369,6 +1369,7 @@ async function renderActiveSettingsTab() {
1369
1369
  case 'email': await renderEmailPane(); break;
1370
1370
  case 'integrations': await renderIntegrationsPane(); break;
1371
1371
  case 'websearch': await renderWebSearchPane(); break;
1372
+ case 'typesafe': await renderTypesafePane(); break;
1372
1373
  case 'mcp': await renderMcpPane(); break;
1373
1374
  case 'events': await renderEventsPane(); break;
1374
1375
  case 'empati': await renderEmpatiPane(); break;
@@ -1459,7 +1460,7 @@ function switchTab(name) {
1459
1460
  document.querySelectorAll('#setTabs .tab').forEach((b) =>
1460
1461
  b.classList.toggle('active', b.dataset.tab === name)
1461
1462
  );
1462
- for (const p of ['lang', 'provider', 'fallout', 'skills', 'agents', 'tts', 'install', 'email', 'integrations', 'websearch', 'mcp', 'events', 'empati', 'cron', 'agenda', 'usage', 'squeeze', 'logs', 'dash', 'sec', 'update']) {
1463
+ for (const p of ['lang', 'provider', 'fallout', 'skills', 'agents', 'tts', 'install', 'email', 'integrations', 'websearch', 'typesafe', 'mcp', 'events', 'empati', 'cron', 'agenda', 'usage', 'squeeze', 'logs', 'dash', 'sec', 'update']) {
1463
1464
  const el = $('#tab-' + p);
1464
1465
  if (el) el.hidden = p !== name; // guard: eksik pane tüm sekmeleri kilitlemesin
1465
1466
  }
@@ -1478,6 +1479,7 @@ function switchTab(name) {
1478
1479
  if (name === 'update') renderUpdatePane(true);
1479
1480
  if (name === 'agents') refreshAgentsPane();
1480
1481
  if (name === 'websearch') renderWebSearchPane();
1482
+ if (name === 'typesafe') renderTypesafePane();
1481
1483
  if (name === 'mcp') renderMcpPane();
1482
1484
  /* Fallout: her açılışta güncel provider zincirini çek */
1483
1485
  if (name === 'fallout') refreshFalloutPane();
@@ -1680,6 +1682,65 @@ async function renderWebSearchPane() {
1680
1682
  });
1681
1683
  }
1682
1684
 
1685
+ /* TypeSafe sekmesi (System One / Jev): API anahtarı + model + bağlantı testi.
1686
+ Anahtar kayıtlıysa ajanlar typesafe_decision aracıyla tipli kararlar alır;
1687
+ Beast Finance rol→skill eşleştirmesinde "typesafe-ai" seçilirse kullanılır. */
1688
+ async function renderTypesafePane() {
1689
+ const pane = $('#tab-typesafe');
1690
+ if (!pane) return;
1691
+ const ts = await beast.typesafeGet().catch(() => ({ set: false, apiKey: '', model: 'jev-latest' }));
1692
+ pane.innerHTML =
1693
+ '<h2>' + _t('ts_h2') + '</h2>' +
1694
+ '<div class="sub">' + _t('ts_sub') + '</div>' +
1695
+ '<div id="tsStatus" class="sub" style="text-align:left;margin-top:8px"></div>' +
1696
+ '<label class="mem-label">' + _t('ts_key_label') + '</label>' +
1697
+ '<input id="tsKeyInp" class="inp" type="password" placeholder="ts_..." autocomplete="new-password" spellcheck="false" />' +
1698
+ '<label class="mem-label" style="margin-top:10px">' + _t('ts_model_label') + '</label>' +
1699
+ '<input id="tsModelInp" class="inp" type="text" placeholder="jev-latest" spellcheck="false" />' +
1700
+ '<div class="form-grid" style="grid-template-columns:auto auto auto;gap:8px;margin-top:10px">' +
1701
+ '<button id="tsSave" class="btn">' + _t('ts_save') + '</button>' +
1702
+ '<button id="tsTest" class="btn ghost">' + _t('ts_test') + '</button>' +
1703
+ '<button id="tsClear" class="btn ghost">' + _t('ts_clear') + '</button></div>' +
1704
+ '<div class="sub" style="margin-top:10px">' + _t('ts_hint') + '</div>';
1705
+ const tsSt = $('#tsStatus');
1706
+ const tsModel = $('#tsModelInp');
1707
+ tsModel.value = ts.model || 'jev-latest';
1708
+ const setTsSt = (r) => {
1709
+ tsSt.textContent = r.set ? _t('ts_status_set') + r.model : _t('ts_status_unset');
1710
+ };
1711
+ setTsSt(ts);
1712
+ const tsTest = $('#tsTest');
1713
+ const runTest = async () => {
1714
+ tsTest.disabled = true;
1715
+ const old = tsTest.textContent;
1716
+ tsTest.textContent = _t('ts_testing');
1717
+ const r = await beast.typesafeTest({ apiKey: $('#tsKeyInp').value.trim(), model: tsModel.value.trim() }).catch(() => null);
1718
+ tsTest.disabled = false;
1719
+ tsTest.textContent = old;
1720
+ if (r && r.ok) toast(_t('ts_test_ok') + (r.model || ''));
1721
+ else toast(_t('ts_test_fail') + ((r && r.error) || '?'));
1722
+ };
1723
+ tsTest.addEventListener('click', runTest);
1724
+ $('#tsSave').addEventListener('click', async () => {
1725
+ const v = $('#tsKeyInp').value.trim();
1726
+ if (!v) { toast(_t('ts_empty_toast')); return; }
1727
+ const rr = await beast.typesafeSet({ apiKey: v, model: tsModel.value.trim() }).catch(() => null);
1728
+ $('#tsKeyInp').value = '';
1729
+ if (rr && rr.ok) {
1730
+ setTsSt(rr.typesafe || {});
1731
+ toast(_t('ts_saved_toast'));
1732
+ } else {
1733
+ toast(_t('ts_fail_toast'));
1734
+ }
1735
+ });
1736
+ $('#tsClear').addEventListener('click', async () => {
1737
+ await beast.typesafeSet({ apiKey: '', model: tsModel.value.trim() }).catch(() => {});
1738
+ $('#tsKeyInp').value = '';
1739
+ setTsSt({ set: false, model: tsModel.value.trim() || 'jev-latest' });
1740
+ toast(_t('ts_cleared_toast'));
1741
+ });
1742
+ }
1743
+
1683
1744
  async function renderProviderPane() {
1684
1745
  const pane = $('#tab-provider');
1685
1746
  pane.innerHTML =
@@ -10954,8 +11015,9 @@ function finSymPickerClose() {
10954
11015
 
10955
11016
  /* ---------- ROL → SKILL eşleştirme modalı ----------
10956
11017
  Roller sunucudan (snapshot.roles), skill listesi KURULU katalogdan gelir —
10957
- yeni skill eklendiğinde modalda otomatik görünür. Seçim finance ayarına
10958
- (roleSkills) yazılır; rol ajanı her turda o skill'leri okumakla yükümlüdür. */
11018
+ yeni skill eklendiğinde modalda otomatik görünür. Rol başına EN FAZLA 2
11019
+ skill seçilir; seçim finance ayarına (roleSkills) yazılır; rol ajanı her
11020
+ turda o skill'leri okumakla yükümlüdür (ör. price-action + typesafe-ai). */
10959
11021
  let finSkillNames = [];
10960
11022
 
10961
11023
  async function finSkillsOpen() {
@@ -10976,7 +11038,7 @@ function finRenderSkills() {
10976
11038
  if (!box) return;
10977
11039
  const cfg = finCfgCache || {};
10978
11040
  const map = cfg.roleSkills && typeof cfg.roleSkills === 'object' ? cfg.roleSkills : {};
10979
- /* ANA KARAR VERİCİ (trader) da bir satır: playbook skill'i zorunlu tek seçim */
11041
+ /* ANA KARAR VERİCİ (trader) da bir satır: playbook skill'leri (en fazla 2) */
10980
11042
  const roles = [
10981
11043
  { id: 'trader', label: 'TRADER (Ana Karar Verici)', desc: 'playbook: hangi setup\'lar serbest, giriş/çıkış/iptal kuralları, seans ve risk disiplini — asıl karar vericinin skill\'i (boş = ajan kendi seçer)' },
10982
11044
  ].concat(Array.isArray(finRolesCatalog) ? finRolesCatalog : []);
@@ -10997,22 +11059,21 @@ function finRenderSkills() {
10997
11059
  head.innerHTML =
10998
11060
  '<b>' + escapeHtml(String(r.label || r.id)) + '</b>' +
10999
11061
  '<span>' + escapeHtml(String(r.desc || '').slice(0, 110)) + '</span>';
11000
- const cur = Array.isArray(map[r.id]) ? map[r.id].map((x) => String(x || '').trim()).filter(Boolean) : [];
11001
- const active = cur.length ? cur[0] : '';
11002
- /* ÇÖP: rolün skill atamasını kaldır — boş bırakmak da geçerli bir seçim
11003
- (o zaman ajan skill'i göreve göre SKILLS kataloğundan kendi seçer) */
11062
+ const cur = Array.isArray(map[r.id]) ? map[r.id].map((x) => String(x || '').trim()).filter(Boolean).slice(0, 2) : [];
11063
+ /* ÇÖP: rolün TÜM skill atamasını kaldır — boş bırakmak da geçerli bir
11064
+ seçim (o zaman ajan skill'i göreve göre SKILLS kataloğundan kendi seçer) */
11004
11065
  const clearBtn = document.createElement('button');
11005
11066
  clearBtn.type = 'button';
11006
11067
  clearBtn.className = 'fin-skill-clear';
11007
- clearBtn.title = active
11008
- ? 'Skill atamasını kaldır (' + active + ') — boş kalırsa ajan göreve göre kendi seçer'
11068
+ clearBtn.title = cur.length
11069
+ ? 'Skill atamalarını kaldır (' + cur.join(', ') + ') — boş kalırsa ajan göreve göre kendi seçer'
11009
11070
  : 'Atama yok — boş bırakılabilir';
11010
11071
  clearBtn.innerHTML =
11011
11072
  '<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>';
11012
11073
  clearBtn.addEventListener('click', (e) => {
11013
11074
  e.preventDefault();
11014
11075
  e.stopPropagation();
11015
- const was = active;
11076
+ const was = cur.join(', ');
11016
11077
  finCfgCache = finCfgCache || {};
11017
11078
  finCfgCache.roleSkills = finCfgCache.roleSkills || {};
11018
11079
  finCfgCache.roleSkills[r.id] = [];
@@ -11023,38 +11084,64 @@ function finRenderSkills() {
11023
11084
  finRenderSkills();
11024
11085
  });
11025
11086
  head.appendChild(clearBtn);
11026
- if (!active) {
11087
+ if (!cur.length) {
11027
11088
  const warn = document.createElement('span');
11028
11089
  warn.className = 'fin-skill-req';
11029
11090
  warn.textContent = 'atama yok — ajan göreve göre kendi seçer';
11030
11091
  head.appendChild(warn);
11092
+ } else if (cur.length > 1) {
11093
+ const cnt = document.createElement('span');
11094
+ cnt.className = 'fin-skill-req';
11095
+ cnt.textContent = cur.length + '/2 skill';
11096
+ head.appendChild(cnt);
11031
11097
  }
11032
11098
  row.appendChild(head);
11033
11099
  const chips = document.createElement('div');
11034
11100
  chips.className = 'fin-skill-chips';
11035
11101
  for (const s of finSkillNames) {
11036
- const on = active === s.name;
11102
+ const on = cur.includes(s.name);
11037
11103
  const chip = document.createElement('label');
11038
11104
  chip.className = 'fin-skill-chip' + (on ? ' on' : '');
11039
11105
  chip.title = s.description || s.name;
11040
11106
  const cb = document.createElement('input');
11041
- cb.type = 'radio';
11042
- cb.name = 'fin-skill-role-' + r.id;
11107
+ cb.type = 'checkbox';
11043
11108
  cb.checked = on;
11044
- /* TEK seçim: yeni skill seçilir; kaldırmak için çöp ikonu kullanılır */
11109
+ /* EN FAZLA 2 seçim: yeni skill işaretlenir, sınır aşılırsa uyarır;
11110
+ kaldırmak için işaret kaldırılır ya da çöp ikonu kullanılır */
11045
11111
  cb.addEventListener('change', () => {
11046
- if (!cb.checked) return;
11047
- const next = [s.name];
11048
- /* yerel önbelleğe anında yaz — 3 sn'lik snapshot eski değeri geri getirmesin */
11049
11112
  finCfgCache = finCfgCache || {};
11050
11113
  finCfgCache.roleSkills = finCfgCache.roleSkills || {};
11114
+ const curNow = (Array.isArray(finCfgCache.roleSkills[r.id]) ? finCfgCache.roleSkills[r.id] : []).map((x) => String(x || '').trim()).filter(Boolean);
11115
+ let next;
11116
+ if (cb.checked) {
11117
+ if (curNow.length >= 2) {
11118
+ cb.checked = false;
11119
+ toast((r.label || r.id) + ' → en fazla 2 skill; önce birini kaldır');
11120
+ return;
11121
+ }
11122
+ next = [...curNow, s.name];
11123
+ } else {
11124
+ next = curNow.filter((x) => x !== s.name);
11125
+ }
11126
+ next = next.slice(0, 2);
11127
+ /* yerel önbelleğe anında yaz — 3 sn'lik snapshot eski değeri geri getirmesin */
11051
11128
  finCfgCache.roleSkills[r.id] = next;
11052
- chips.querySelectorAll('.fin-skill-chip').forEach((el) => el.classList.remove('on'));
11053
- chip.classList.add('on');
11129
+ chip.classList.toggle('on', next.includes(s.name));
11054
11130
  const w = head.querySelector('.fin-skill-req');
11055
11131
  if (w) w.remove();
11132
+ if (!next.length) {
11133
+ const warn = document.createElement('span');
11134
+ warn.className = 'fin-skill-req';
11135
+ warn.textContent = 'atama yok — ajan göreve göre kendi seçer';
11136
+ head.appendChild(warn);
11137
+ } else if (next.length > 1) {
11138
+ const cnt = document.createElement('span');
11139
+ cnt.className = 'fin-skill-req';
11140
+ cnt.textContent = next.length + '/2 skill';
11141
+ head.appendChild(cnt);
11142
+ }
11056
11143
  finSaveCfg({ roleSkills: { [r.id]: next } });
11057
- toast((r.label || r.id) + ' → ' + s.name + ' (tek skill)');
11144
+ toast((r.label || r.id) + ' → ' + (next.length ? next.join(' + ') : 'atama kaldırıldı') + ' (' + next.length + '/2)');
11058
11145
  });
11059
11146
  chip.appendChild(cb);
11060
11147
  chip.appendChild(document.createTextNode(s.name));
@@ -11123,6 +11210,12 @@ function finLearnStatsText(st) {
11123
11210
  if (Number(st.streak) >= 2) parts.push(Number(st.streak) + ' ardışık kayıp');
11124
11211
  if (Number(st.givebacks) > 0) parts.push(Number(st.givebacks) + ' kez kâr geri verildi');
11125
11212
  if (st.lastAt) parts.push('son ' + finLearnSign(st.lastNet));
11213
+ /* EN İYİ PERİYOT: bot zaman dilimini buna göre seçer */
11214
+ const tfRows = Object.entries((st && st.byTf) || {}).filter(([, s]) => s && Number(s.trades) > 0);
11215
+ if (tfRows.length) {
11216
+ tfRows.sort((a, b) => (Number(b[1].net) || 0) - (Number(a[1].net) || 0));
11217
+ parts.push('en iyi ' + tfRows[0][0]);
11218
+ }
11126
11219
  return parts.join(' · ');
11127
11220
  }
11128
11221
 
@@ -11271,6 +11364,47 @@ function finLearnDetail(body, sym, d) {
11271
11364
  info.textContent = bits.join(' — ');
11272
11365
  body.appendChild(info);
11273
11366
 
11367
+ /* PERİYOT KIRILIMI: hangi zaman dilimi kazandırıyor — bot işlem periyodunu
11368
+ (timeframe) buna göre SEÇER; negatif periyotta risk düşürülür */
11369
+ const tfRows = Object.entries(st.byTf || {}).filter(([, s]) => s && Number(s.trades) > 0);
11370
+ if (tfRows.length) {
11371
+ const tfHead = document.createElement('div');
11372
+ tfHead.className = 'fin-learn-sec';
11373
+ tfHead.textContent = 'ZAMAN DİLİMLERİ (işlem periyodunu bot seçer — en iyi net üstte)';
11374
+ body.appendChild(tfHead);
11375
+ tfRows.sort((a, b) => (Number(b[1].net) || 0) - (Number(a[1].net) || 0));
11376
+ for (const [tf, s] of tfRows) {
11377
+ const tr = document.createElement('div');
11378
+ tr.className = 'fin-learn-trade ' + (Number(s.net) < 0 ? 'loss' : 'win');
11379
+ const w = Math.round((Number(s.wins) / Number(s.trades)) * 100);
11380
+ tr.textContent = `${tf} · ${s.trades} işlem · %${w} kazanç · net ${finLearnSign(s.net)}` +
11381
+ (Number(s.givebacks) > 0 ? ` · ${Number(s.givebacks)} kâr geri verme` : '') +
11382
+ (Number(s.net) < 0 ? ' · NEGATİF: riski düşür/teyit ara' : '');
11383
+ body.appendChild(tr);
11384
+ }
11385
+ }
11386
+
11387
+ /* YAPILAN YANLIŞLAR: zararla kapananlar otomatik kaydedilir — ajan bunlardan
11388
+ hata dersi (kind:"mistake") çıkarır; tekrar eden kalıp burada görünür */
11389
+ const mistakes = (d && Array.isArray(d.mistakes)) ? d.mistakes : [];
11390
+ if (mistakes.length) {
11391
+ const mHead = document.createElement('div');
11392
+ mHead.className = 'fin-learn-sec';
11393
+ mHead.textContent = 'YAPILAN YANLIŞLAR (' + mistakes.length + ' — ders çıkar, tekrarlama)';
11394
+ body.appendChild(mHead);
11395
+ for (const m of mistakes.slice().reverse()) {
11396
+ const tr = document.createElement('div');
11397
+ tr.className = 'fin-learn-trade loss';
11398
+ const side = String(m.side || '').toLowerCase();
11399
+ const sideTxt = side === 'buy' ? 'AL' : side === 'sell' ? 'SAT' : (side || '?');
11400
+ tr.textContent = `${finLearnTime(m.at)} · ${m.tf || 'periyot?'} · ${sideTxt} · ${finLearnSign(m.net)}` +
11401
+ (m.reason ? ` · ${String(m.reason)}` : '') +
11402
+ (m.giveback ? ' · kâr geri verildi' : '') +
11403
+ (m.mae != null ? ` · MAE ${finLearnSign(m.mae)}` : '');
11404
+ body.appendChild(tr);
11405
+ }
11406
+ }
11407
+
11274
11408
  /* KALICI ÖZET (opencode tarzı compaction): eski derslerin modele
11275
11409
  özetlettirilmiş hâli — ham dersler silinse de bilgi burada kalır */
11276
11410
  const summ = d && d.summary && d.summary.text ? d.summary : null;
@@ -11307,6 +11441,13 @@ function finLearnDetail(body, sym, d) {
11307
11441
  const kind = document.createElement('span');
11308
11442
  kind.className = 'fin-learn-kind k-' + String(n.kind || 'observation');
11309
11443
  kind.textContent = finLearnKindLabel(n.kind);
11444
+ let tfEl = null;
11445
+ if (n.tf) {
11446
+ tfEl = document.createElement('span');
11447
+ tfEl.className = 'fin-learn-kind';
11448
+ tfEl.textContent = String(n.tf);
11449
+ tfEl.title = 'Bu dersin zaman dilimi';
11450
+ }
11310
11451
  const txt = document.createElement('span');
11311
11452
  txt.className = 'fin-learn-note-text';
11312
11453
  txt.textContent = String(n.text || '');
@@ -11329,6 +11470,7 @@ function finLearnDetail(body, sym, d) {
11329
11470
  }
11330
11471
  });
11331
11472
  rowEl.appendChild(kind);
11473
+ if (tfEl) rowEl.appendChild(tfEl);
11332
11474
  rowEl.appendChild(txt);
11333
11475
  rowEl.appendChild(at);
11334
11476
  rowEl.appendChild(del);
@@ -11347,7 +11489,7 @@ function finLearnDetail(body, sym, d) {
11347
11489
  tr.className = 'fin-learn-trade ' + (Number(t.net) < 0 ? 'loss' : 'win');
11348
11490
  const side = String(t.side || '').toLowerCase();
11349
11491
  const sideTxt = side === 'buy' ? 'AL' : side === 'sell' ? 'SAT' : (side || '?');
11350
- tr.textContent = `${finLearnTime(t.at)} · ${sideTxt} · ${finLearnSign(t.net)}` +
11492
+ tr.textContent = `${finLearnTime(t.at)} · ${t.tf ? t.tf + ' · ' : ''}${sideTxt} · ${finLearnSign(t.net)}` +
11351
11493
  (t.mfe != null ? ` · MFE ${finLearnSign(t.mfe)}` : '') +
11352
11494
  (t.mae != null ? ` · MAE ${finLearnSign(t.mae)}` : '') +
11353
11495
  (t.reason ? ` · ${String(t.reason)}` : '');