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/package.json +1 -1
- package/src/agent/engine.js +33 -16
- package/src/agent/financetools.js +44 -17
- package/src/agent/seeds/typesafe-ai/SKILL.md +60 -0
- package/src/agent/tools.js +5 -2
- package/src/agent/typesafe.js +278 -0
- package/src/main.js +766 -77
- package/src/preload.js +4 -0
- package/src/renderer/i18n.js +38 -0
- package/src/renderer/index.html +3 -1
- package/src/renderer/renderer.js +165 -23
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'),
|
package/src/renderer/i18n.js
CHANGED
|
@@ -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
|
|
package/src/renderer/index.html
CHANGED
|
@@ -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
|
|
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>
|
package/src/renderer/renderer.js
CHANGED
|
@@ -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.
|
|
10958
|
-
(roleSkills) yazılır; rol ajanı her
|
|
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'
|
|
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
|
-
|
|
11002
|
-
|
|
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 =
|
|
11008
|
-
? 'Skill
|
|
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 =
|
|
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 (!
|
|
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 =
|
|
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 = '
|
|
11042
|
-
cb.name = 'fin-skill-role-' + r.id;
|
|
11107
|
+
cb.type = 'checkbox';
|
|
11043
11108
|
cb.checked = on;
|
|
11044
|
-
/*
|
|
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
|
-
|
|
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) + ' → ' +
|
|
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)}` : '');
|