beast-agent 2.31.0 → 2.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -194,6 +194,31 @@ const els = {
194
194
  finSymSearch: $('#finSymSearch'),
195
195
  finSymPick: $('#finSymPick'),
196
196
  finSymCount: $('#finSymCount'),
197
+ finWatchdogCb: $('#finWatchdog'),
198
+ finBeOnR: $('#finBeOnR'),
199
+ finTrailStartR: $('#finTrailStartR'),
200
+ finTrailR: $('#finTrailR'),
201
+ finPartialR: $('#finPartialR'),
202
+ finPartialPct: $('#finPartialPct'),
203
+ finMaxDailyLoss: $('#finMaxDailyLoss'),
204
+ finRiskPct: $('#finRiskPct'),
205
+ finMaxPerSymbol: $('#finMaxPerSymbol'),
206
+ finMaxSameSide: $('#finMaxSameSide'),
207
+ finMinMarginLevel: $('#finMinMarginLevel'),
208
+ finDailyLossAction: $('#finDailyLossAction'),
209
+ finNotifyTarget: $('#finNotifyTarget'),
210
+ finWatchDot: $('#finWatchDot'),
211
+ finWatchInfo: $('#finWatchInfo'),
212
+ finAlertList: $('#finAlertList'),
213
+ finReportBtn: $('#finReportBtn'),
214
+ finStatsInfo: $('#finStatsInfo'),
215
+ finStatToday: $('#finStatToday'),
216
+ finStatWeek: $('#finStatWeek'),
217
+ finStatWin: $('#finStatWin'),
218
+ finStatPf: $('#finStatPf'),
219
+ finStatDd: $('#finStatDd'),
220
+ finStatTrades: $('#finStatTrades'),
221
+ finEquityBox: $('#finEquity'),
197
222
  stVideoEl: $('#stVideoEl'),
198
223
  stVideoName: $('#stVideoName'),
199
224
  stVideoOpen: $('#stVideoOpen'),
@@ -229,6 +254,7 @@ const els = {
229
254
  cronSchedule: $('#cronSchedule'),
230
255
  cronPrompt: $('#cronPrompt'),
231
256
  cronAddBtn: $('#cronAddBtn'),
257
+ cronCancel: $('#cronCancel'),
232
258
  cronList: $('#cronList'),
233
259
  gearBtn: $('#gearBtn'),
234
260
  settingsOverlay: $('#settingsOverlay'),
@@ -1242,6 +1268,7 @@ async function renderActiveSettingsTab() {
1242
1268
  case 'events': await renderEventsPane(); break;
1243
1269
  case 'empati': await renderEmpatiPane(); break;
1244
1270
  case 'cron': await openCron(); break;
1271
+ case 'agenda': await renderAgendaPane(); break;
1245
1272
  case 'usage': await renderUsagePane(); break;
1246
1273
  case 'headroom': await renderHeadroomPane(); break;
1247
1274
  case 'agents': await refreshAgentsPane(); break;
@@ -1327,12 +1354,14 @@ function switchTab(name) {
1327
1354
  document.querySelectorAll('#setTabs .tab').forEach((b) =>
1328
1355
  b.classList.toggle('active', b.dataset.tab === name)
1329
1356
  );
1330
- for (const p of ['lang', 'provider', 'fallout', 'skills', 'agents', 'tts', 'install', 'email', 'integrations', 'websearch', 'mcp', 'events', 'empati', 'cron', 'usage', 'headroom', 'logs', 'dash', 'sec', 'update']) {
1357
+ for (const p of ['lang', 'provider', 'fallout', 'skills', 'agents', 'tts', 'install', 'email', 'integrations', 'websearch', 'mcp', 'events', 'empati', 'cron', 'agenda', 'usage', 'headroom', 'logs', 'dash', 'sec', 'update']) {
1331
1358
  const el = $('#tab-' + p);
1332
1359
  if (el) el.hidden = p !== name; // guard: eksik pane tüm sekmeleri kilitlemesin
1333
1360
  }
1334
1361
  if (name === 'lang') renderLangPane();
1335
1362
  if (name === 'cron') openCron();
1363
+ if (name === 'agenda') renderAgendaPane();
1364
+ if (name === 'email') renderEmailPane();
1336
1365
  if (name === 'usage') renderUsagePane();
1337
1366
  if (name === 'install') renderInstallPane();
1338
1367
  if (name === 'headroom') renderHeadroomPane();
@@ -2640,7 +2669,25 @@ async function renderEmailPane() {
2640
2669
  <input id="mailSmtpPort" class="inp" placeholder="465" autocomplete="off" />
2641
2670
  <button id="mailSave" class="btn ghost">${_t('mail_save')}</button>
2642
2671
  </div>` +
2643
- '<div class="sub" style="margin-top:8px">' + _t('mail_note') + '</div>';
2672
+ '<div class="sub" style="margin-top:8px">' + _t('mail_note') + '</div>' +
2673
+ '<div class="divider"></div>' +
2674
+ '<h2>' + _t('mail_inbox') + '</h2>' +
2675
+ '<div style="display:flex;gap:8px;margin:6px 0;align-items:center">' +
2676
+ '<button id="mailRefresh" class="btn ghost">' + _t('mail_refresh') + '</button>' +
2677
+ '<button id="mailCompose" class="btn ghost">' + _t('mail_compose') + '</button>' +
2678
+ '<span id="mailMsg" class="sub" style="margin:0"></span></div>' +
2679
+ '<div id="mailList"></div>' +
2680
+ '<div id="mailRead" hidden style="margin-top:10px"></div>' +
2681
+ '<div id="mailWrite" hidden style="margin-top:10px">' +
2682
+ '<div class="form-grid" style="grid-template-columns:1fr 1fr">' +
2683
+ '<input id="mwTo" class="inp" placeholder="' + _t('mail_to') + '" autocomplete="off" />' +
2684
+ '<input id="mwSubject" class="inp" placeholder="' + _t('mail_subject') + '" autocomplete="off" />' +
2685
+ '</div>' +
2686
+ '<textarea id="mwBody" class="inp" rows="6" placeholder="' + _t('mail_body') + '" style="margin-top:6px;width:100%"></textarea>' +
2687
+ '<div style="display:flex;gap:8px;margin-top:6px">' +
2688
+ '<button id="mwSend" class="btn">' + _t('mail_send') + '</button>' +
2689
+ '<button id="mwCancel" class="btn ghost">' + _t('btn_close') + '</button>' +
2690
+ '</div></div>';
2644
2691
 
2645
2692
  try {
2646
2693
  const em = await beast.getEmail();
@@ -2672,6 +2719,86 @@ async function renderEmailPane() {
2672
2719
  if (pass && pass !== PASS_MASK) { savedPass = pass; mailPass.value = PASS_MASK; passChanged = false; }
2673
2720
  toast(_t('mail_saved'));
2674
2721
  });
2722
+
2723
+ /* ---- gelen kutusu + okuma + gönderme ---- */
2724
+ const mailMsg = $('#mailMsg');
2725
+ const mailList = $('#mailList');
2726
+ const mailRead = $('#mailRead');
2727
+ const mailWrite = $('#mailWrite');
2728
+ const openWrite = (to, subject) => {
2729
+ if (!mailWrite) return;
2730
+ mailWrite.hidden = false;
2731
+ $('#mwTo').value = to || '';
2732
+ $('#mwSubject').value = subject || '';
2733
+ $('#mwBody').value = '';
2734
+ $('#mwBody').focus();
2735
+ };
2736
+ const loadInbox = async () => {
2737
+ if (!mailList) return;
2738
+ if (mailMsg) mailMsg.textContent = '…';
2739
+ if (mailRead) mailRead.hidden = true;
2740
+ const r = await beast.emailList({ limit: 15 }).catch((e) => ({ ok: false, error: String((e && e.message) || e) }));
2741
+ if (mailMsg) mailMsg.textContent = '';
2742
+ if (!r || r.ok === false) {
2743
+ mailList.innerHTML = '<div class="sub">' + escapeHtml((r && r.error) || '?') + '</div>';
2744
+ return;
2745
+ }
2746
+ const msgs = r.messages || [];
2747
+ mailList.innerHTML = msgs.length
2748
+ ? msgs
2749
+ .map(
2750
+ (m) =>
2751
+ '<div class="cj-job" data-uid="' + escapeHtml(String(m.uid)) + '" style="cursor:pointer">' +
2752
+ '<div class="cj-info"><div class="cj-name">' + escapeHtml(m.subject || '(konu yok)') + '</div>' +
2753
+ '<div class="cj-meta">' + escapeHtml(m.from || '') + ' · ' + escapeHtml(fmtWhen(m.date)) + '</div></div></div>'
2754
+ )
2755
+ .join('')
2756
+ : '<div class="sub">' + _t('mail_empty') + '</div>';
2757
+ mailList.querySelectorAll('[data-uid]').forEach((row) =>
2758
+ row.addEventListener('click', async () => {
2759
+ const uid = Number(row.dataset.uid);
2760
+ const subjectEl = row.querySelector('.cj-name');
2761
+ if (mailRead) {
2762
+ mailRead.hidden = false;
2763
+ mailRead.innerHTML = '<div class="sub">…</div>';
2764
+ }
2765
+ const rr = await beast.emailRead(uid).catch(() => null);
2766
+ if (!mailRead) return;
2767
+ if (rr && rr.ok) {
2768
+ mailRead.innerHTML =
2769
+ '<div class="sub" style="white-space:pre-wrap;max-height:220px;overflow:auto">' +
2770
+ escapeHtml(rr.content || '') +
2771
+ '</div><button id="mailReply" class="btn ghost" style="margin-top:6px">' + _t('mail_reply') + '</button>';
2772
+ const rp = $('#mailReply');
2773
+ if (rp)
2774
+ rp.addEventListener('click', () =>
2775
+ openWrite('', 'Re: ' + (subjectEl ? subjectEl.textContent : ''))
2776
+ );
2777
+ } else {
2778
+ mailRead.innerHTML = '<div class="sub">' + escapeHtml((rr && rr.error) || _t('mail_read_fail')) + '</div>';
2779
+ }
2780
+ })
2781
+ );
2782
+ };
2783
+ if ($('#mailRefresh')) $('#mailRefresh').addEventListener('click', loadInbox);
2784
+ if ($('#mailCompose')) $('#mailCompose').addEventListener('click', () => openWrite('', ''));
2785
+ if ($('#mwCancel')) $('#mwCancel').addEventListener('click', () => { if (mailWrite) mailWrite.hidden = true; });
2786
+ if ($('#mwSend')) $('#mwSend').addEventListener('click', async () => {
2787
+ const to = $('#mwTo').value.trim();
2788
+ const subject = $('#mwSubject').value.trim();
2789
+ const body = $('#mwBody').value;
2790
+ if (!to) { toast(_t('mail_need_to')); return; }
2791
+ const r = await beast.emailSend({ to, subject, body }).catch((e) => ({ ok: false, error: String((e && e.message) || e) }));
2792
+ if (r && r.ok) {
2793
+ toast(_t('mail_sent'));
2794
+ if (mailWrite) mailWrite.hidden = true;
2795
+ loadInbox();
2796
+ } else {
2797
+ toast((r && r.error) || _t('mail_send_fail'));
2798
+ }
2799
+ });
2800
+ /* ayar kayıtlıysa ve sekme GÖRÜNÜRse gelen kutusunu yükle (boşuna IMAP bağlantısı yok) */
2801
+ if (savedPass && $('#mailUser').value.trim() && !pane.hidden) loadInbox();
2675
2802
  }
2676
2803
 
2677
2804
  /* ---------------- integrations (WhatsApp) ---------------- */
@@ -5572,7 +5699,15 @@ function renderAgentsPane() {
5572
5699
  /* ---------------- events from engine ---------------- */
5573
5700
 
5574
5701
  function onEvent(ev) {
5575
- if (ev.type === 'sessions') { refreshSessions(); return; }
5702
+ if (ev.type === 'notify-click') {
5703
+ /* toast tıklandı: ilgili panele atla */
5704
+ if (ev.target === 'watchers') openWatchModal();
5705
+ else if (ev.target === 'cron') openSettings().then(() => switchTab('cron')).catch(() => {});
5706
+ else if (ev.target === 'agents') { toggleRail(true); refreshAgentsPane(); }
5707
+ else openSettings().catch(() => {});
5708
+ return;
5709
+ }
5710
+ if (ev.type === 'sessions') { refreshSessions(); return; }
5576
5711
  if (ev.type === 'approval') {
5577
5712
  /* Beast Studio oturumunun onayı: panelde ipucu + kart sohbete düşer */
5578
5713
  if (stSessionId && ev.sessionId === stSessionId) {
@@ -6333,6 +6468,13 @@ async function openWatchLogModal(id) {
6333
6468
  }
6334
6469
 
6335
6470
  let lastCronKey = '';
6471
+ /* Cron DÜZENLEME: satıra tıkla → form dolar, buton "Güncelle"ye döner */
6472
+ let editingCronId = '';
6473
+ function resetCronEdit() {
6474
+ editingCronId = '';
6475
+ if (els.cronAddBtn) els.cronAddBtn.textContent = _t('cron_add');
6476
+ if (els.cronCancel) els.cronCancel.hidden = true;
6477
+ }
6336
6478
  async function renderCronModal(force) {
6337
6479
  let jobs = [];
6338
6480
  try { jobs = (await beast.cronList()) || []; } catch {}
@@ -6755,18 +6897,116 @@ function renderCronList(jobs) {
6755
6897
  btns.appendChild(mkBtn(j.enabled ? '❚❚' : '▶', j.enabled ? _t('cr_pause') : _t('cr_start'), async () => { await beast.cronToggle(j.id); refreshCron(); }));
6756
6898
  btns.appendChild(mkBtn('×', _t('cr_del'), async () => { await beast.cronDelete(j.id); refreshCron(); }));
6757
6899
  row.appendChild(btns);
6758
- dot.addEventListener('click', () => {
6759
- els.cronName.value = j.name;
6900
+ const loadEdit = () => {
6901
+ editingCronId = j.id;
6902
+ els.cronName.value = j.name || '';
6760
6903
  els.cronPreset.value = '__custom';
6761
- els.cronSchedule.value = j.schedule;
6762
- els.cronPrompt.value = j.prompt;
6763
- });
6904
+ els.cronSchedule.value = j.schedule || '';
6905
+ els.cronPrompt.value = j.prompt || '';
6906
+ if (els.cronAddBtn) els.cronAddBtn.textContent = _t('cr_update');
6907
+ if (els.cronCancel) els.cronCancel.hidden = false;
6908
+ els.cronName.focus();
6909
+ toast(_t('cr_edit_loaded'));
6910
+ };
6911
+ dot.addEventListener('click', loadEdit);
6912
+ info.addEventListener('click', loadEdit);
6913
+ dot.title = (j.enabled ? _t('cr_dot_on') : _t('cr_dot_off')) + ' — ' + _t('cr_edit_hint');
6764
6914
  els.cronList.appendChild(row);
6765
6915
  }
6766
6916
  }
6767
6917
 
6768
6918
  function openCron() { refreshCron(); }
6769
6919
 
6920
+ /* ---------------- AJANDA: yaklaşan cron + hatırlatıcı + izleyiciler ---------------- */
6921
+
6922
+ function isReminderLike(j) {
6923
+ if (!j) return false;
6924
+ if (j.kind === 'reminder') return true;
6925
+ const n = String(j.name || '');
6926
+ const p = String(j.prompt || '');
6927
+ return (
6928
+ /^Hatırlatma:/.test(n) ||
6929
+ /^Tekrarlı hatırlatma:/.test(n) ||
6930
+ p.startsWith('[HATIRLATMA ZAMANI]') ||
6931
+ p.startsWith('[TEKRARLI HATIRLATMA]')
6932
+ );
6933
+ }
6934
+
6935
+ function relTime(ms) {
6936
+ const diff = ms - Date.now();
6937
+ const abs = Math.abs(diff);
6938
+ const dk = Math.round(abs / 60000);
6939
+ const saat = Math.floor(dk / 60);
6940
+ const gun = Math.floor(saat / 24);
6941
+ let label;
6942
+ if (dk < 1) return _t('aj_now');
6943
+ else if (dk < 60) label = dk + ' dk';
6944
+ else if (saat < 24) label = saat + ' sa';
6945
+ else label = gun + ' gün';
6946
+ return diff >= 0 ? label + ' ' + _t('aj_later') : label + ' ' + _t('aj_ago');
6947
+ }
6948
+
6949
+ async function renderAgendaPane() {
6950
+ const pane = $('#tab-agenda');
6951
+ if (!pane) return;
6952
+ let jobs = [];
6953
+ let ws = [];
6954
+ try { jobs = (await beast.cronList()) || []; } catch {}
6955
+ try { ws = (await beast.watchersList()) || []; } catch {}
6956
+ const items = [];
6957
+ for (const j of jobs) {
6958
+ if (!j.enabled) continue;
6959
+ const t = j.nextRunAt ? new Date(j.nextRunAt).getTime() : 0;
6960
+ if (!t) continue;
6961
+ items.push({ type: isReminderLike(j) ? 'rem' : 'cron', name: j.name || j.id, at: t, meta: j.schedule, id: j.id });
6962
+ }
6963
+ for (const w of ws) {
6964
+ if (!w.enabled) continue;
6965
+ const every = w.everySec ? w.everySec * 1000 : (w.everyMin || 15) * 60000;
6966
+ const last = w.lastCheckAt ? new Date(w.lastCheckAt).getTime() : 0;
6967
+ items.push({
6968
+ type: 'watch',
6969
+ name: w.name,
6970
+ at: last ? last + every : Date.now(),
6971
+ meta: opText(w) + ' · her ' + fmtEvery(w),
6972
+ id: w.id,
6973
+ });
6974
+ }
6975
+ items.sort((a, b) => a.at - b.at);
6976
+ const badge = { cron: '🕐', rem: '⏰', watch: '👁' };
6977
+ const rows = items.length
6978
+ ? items
6979
+ .map(
6980
+ (it) =>
6981
+ '<div class="cron-job ag-item" data-type="' + it.type + '" data-id="' + escapeHtml(it.id) + '" style="cursor:pointer">' +
6982
+ '<div class="cj-info"><div class="cj-name">' + badge[it.type] + ' ' + escapeHtml(it.name) + '</div>' +
6983
+ '<div class="cj-meta">' + escapeHtml(String(it.meta || '')) + ' · <b>' + escapeHtml(relTime(it.at)) + '</b> · ' + escapeHtml(fmtTime(it.at)) + '</div></div></div>'
6984
+ )
6985
+ .join('')
6986
+ : '<div class="cron-empty">' + _t('aj_empty') + '</div>';
6987
+ let notifOn = true;
6988
+ try { notifOn = !!(await beast.notifyGet()).toast; } catch {}
6989
+ pane.innerHTML =
6990
+ '<h2>' + _t('aj_h2') + '</h2><div class="sub">' + _t('aj_sub') + '</div>' +
6991
+ '<label class="lock-row" style="margin-top:8px"><input type="checkbox" id="ajNotify" ' + (notifOn ? 'checked' : '') + '/><span>' + _t('aj_notify') + '</span></label>' +
6992
+ '<div style="margin-top:10px">' + rows + '</div>';
6993
+ const nj = $('#ajNotify');
6994
+ if (nj) nj.addEventListener('change', async (e) => {
6995
+ await beast.notifySet(e.target.checked).catch(() => {});
6996
+ toast(e.target.checked ? _t('aj_notify_on') : _t('aj_notify_off'));
6997
+ });
6998
+ pane.querySelectorAll('.ag-item').forEach((row) => {
6999
+ row.addEventListener('click', () => {
7000
+ if (row.dataset.type === 'watch') {
7001
+ closeSettings();
7002
+ openWatchModal();
7003
+ } else {
7004
+ switchTab('cron');
7005
+ }
7006
+ });
7007
+ });
7008
+ }
7009
+
6770
7010
  /* ---------------- boot ---------------- */
6771
7011
 
6772
7012
  async function init() {
@@ -7161,6 +7401,59 @@ async function init() {
7161
7401
  if (els.watchLogOverlay) els.watchLogOverlay.addEventListener('click', (e) => {
7162
7402
  if (e.target === els.watchLogOverlay) closeWatchLogModal();
7163
7403
  });
7404
+
7405
+ /* watcher EKLEME formu: tür seçimine göre alanlar açılır/kapanır */
7406
+ const wKindSel = $('#wAddKind');
7407
+ const wSyncKind = () => {
7408
+ const k = wKindSel ? wKindSel.value : 'web';
7409
+ const web = $('#wAddWeb');
7410
+ const cond = $('#wAddCond');
7411
+ const logs = $('#wAddLogs');
7412
+ if (web) web.hidden = k !== 'web';
7413
+ if (cond) cond.hidden = k === 'logs';
7414
+ if (logs) logs.hidden = k !== 'logs';
7415
+ };
7416
+ if (wKindSel) {
7417
+ wKindSel.addEventListener('change', wSyncKind);
7418
+ wSyncKind();
7419
+ }
7420
+ const wAddBtn = $('#wAddBtn');
7421
+ if (wAddBtn) {
7422
+ wAddBtn.addEventListener('click', async () => {
7423
+ const kind = wKindSel ? wKindSel.value : 'web';
7424
+ const input = {
7425
+ name: $('#wAddName').value.trim(),
7426
+ kind,
7427
+ everyMin: Number($('#wAddEvery').value) || 15,
7428
+ };
7429
+ if (kind === 'web') {
7430
+ input.url = $('#wAddUrl').value.trim();
7431
+ input.path = $('#wAddPath').value.trim();
7432
+ }
7433
+ if (kind !== 'logs') {
7434
+ input.op = $('#wAddOp').value;
7435
+ const v = $('#wAddValue').value.trim();
7436
+ input.value = input.op === 'changed' || v === '' ? null : (Number.isNaN(Number(v)) ? v : Number(v));
7437
+ } else {
7438
+ input.level = $('#wAddLevel').value;
7439
+ input.windowMin = Number($('#wAddWindow').value) || 10;
7440
+ input.re = $('#wAddRe').value.trim();
7441
+ input.op = 'gt';
7442
+ input.value = 0;
7443
+ }
7444
+ const r = await beast.watchersAdd(input).catch((e) => ({ ok: false, error: String((e && e.message) || e) }));
7445
+ if (!r || r.ok === false) { toast((r && r.error) || 'İzleyici eklenemedi'); return; }
7446
+ for (const id of ['#wAddName', '#wAddUrl', '#wAddPath', '#wAddValue', '#wAddRe']) {
7447
+ const el = $(id);
7448
+ if (el) el.value = '';
7449
+ }
7450
+ lastWatchKey = '';
7451
+ lastWatchPaneKey = '';
7452
+ await renderWatchersModal(true);
7453
+ await renderWatchersPane(true);
7454
+ toast(_t('w_added'));
7455
+ });
7456
+ }
7164
7457
  if (els.cronOverlay) els.cronOverlay.addEventListener('click', (e) => {
7165
7458
  if (e.target === els.cronOverlay) closeCronModal();
7166
7459
  });
@@ -7173,12 +7466,26 @@ async function init() {
7173
7466
  const schedule = els.cronSchedule.value.trim();
7174
7467
  const prompt = els.cronPrompt.value.trim();
7175
7468
  if (!schedule || !prompt) { toast('Cron ifadesi ve görev metni gerekli'); return; }
7176
- const r = await beast.cronAdd({ name, schedule, prompt });
7177
- if (!r.ok) { toast(r.error || 'Eklenemedi'); return; }
7469
+ if (editingCronId) {
7470
+ const r = await beast.cronUpdate(editingCronId, { name, schedule, prompt });
7471
+ if (!r || r.ok === false) { toast((r && r.error) || 'Güncellenemedi'); return; }
7472
+ toast(_t('cr_updated'));
7473
+ } else {
7474
+ const r = await beast.cronAdd({ name, schedule, prompt });
7475
+ if (!r.ok) { toast(r.error || 'Eklenemedi'); return; }
7476
+ toast(_t('cr_added'));
7477
+ }
7178
7478
  els.cronName.value = ''; els.cronSchedule.value = ''; els.cronPrompt.value = '';
7179
- toast('Görev eklendi');
7479
+ resetCronEdit();
7180
7480
  refreshCron();
7181
7481
  });
7482
+ if (els.cronCancel) {
7483
+ els.cronCancel.addEventListener('click', () => {
7484
+ els.cronName.value = ''; els.cronSchedule.value = ''; els.cronPrompt.value = '';
7485
+ resetCronEdit();
7486
+ toast(_t('cr_edit_cancelled'));
7487
+ });
7488
+ }
7182
7489
  document.addEventListener('mousemove', (e) => {
7183
7490
  if (!rz) return;
7184
7491
  const w = Math.max(300, Math.min(rz.sw - (e.clientX - rz.sx), window.innerWidth - 340));
@@ -9074,6 +9381,116 @@ function finRenderTrader(trader, cfg) {
9074
9381
  }
9075
9382
  }
9076
9383
 
9384
+ /* ---- RİSK OTOMASYONU + PERFORMANS panosu ---- */
9385
+ function finDayKey(ms) {
9386
+ const d = new Date(Number(ms) || 0);
9387
+ const p = (x) => String(x).padStart(2, '0');
9388
+ return d.getFullYear() + '-' + p(d.getMonth() + 1) + '-' + p(d.getDate());
9389
+ }
9390
+
9391
+ function finRenderAutomation(cfg, watch) {
9392
+ if (!cfg) return;
9393
+ const ae = document.activeElement;
9394
+ if (els.finWatchdogCb) els.finWatchdogCb.checked = cfg.watchdog !== false;
9395
+ const setNum = (el, v) => {
9396
+ if (el && ae !== el) el.value = v;
9397
+ };
9398
+ setNum(els.finBeOnR, cfg.beOnR != null ? cfg.beOnR : 1);
9399
+ setNum(els.finTrailStartR, cfg.trailStartR != null ? cfg.trailStartR : 1.5);
9400
+ setNum(els.finTrailR, cfg.trailR != null ? cfg.trailR : 0.5);
9401
+ setNum(els.finPartialR, cfg.partialR != null ? cfg.partialR : 0);
9402
+ setNum(els.finPartialPct, cfg.partialPct != null ? cfg.partialPct : 50);
9403
+ setNum(els.finMaxDailyLoss, cfg.maxDailyLossPct != null ? cfg.maxDailyLossPct : 3);
9404
+ setNum(els.finRiskPct, cfg.riskPerTradePct != null ? cfg.riskPerTradePct : 1);
9405
+ setNum(els.finMaxPerSymbol, cfg.maxPerSymbol != null ? cfg.maxPerSymbol : 2);
9406
+ setNum(els.finMaxSameSide, cfg.maxSameSide != null ? cfg.maxSameSide : 3);
9407
+ setNum(els.finMinMarginLevel, cfg.minMarginLevel != null ? cfg.minMarginLevel : 150);
9408
+ if (els.finDailyLossAction && ae !== els.finDailyLossAction) els.finDailyLossAction.value = cfg.dailyLossAction || 'stop';
9409
+ if (els.finNotifyTarget && ae !== els.finNotifyTarget) els.finNotifyTarget.value = cfg.notifyTarget || 'auto';
9410
+ if (els.finWatchDot) {
9411
+ els.finWatchDot.classList.remove('on', 'off', 'busy');
9412
+ els.finWatchDot.classList.add(watch && watch.on ? 'on' : 'off');
9413
+ els.finWatchDot.title = watch && watch.on ? 'Koruma döngüsü çalışıyor (5 sn)' : 'Koruma döngüsü kapalı';
9414
+ }
9415
+ if (els.finWatchInfo) {
9416
+ els.finWatchInfo.textContent = watch && watch.on ? '· izlenen ' + (watch.managed || 0) : '';
9417
+ }
9418
+ }
9419
+
9420
+ function finSparkline(points) {
9421
+ const list = (Array.isArray(points) ? points : []).filter((p) => p && isFinite(Number(p.equity)));
9422
+ if (list.length < 2) return '<div class="fin-empty">Equity verisi toplanıyor…</div>';
9423
+ const w = 100;
9424
+ const h = 28;
9425
+ const vals = list.map((p) => Number(p.equity));
9426
+ let min = Math.min(...vals);
9427
+ let max = Math.max(...vals);
9428
+ if (max - min < 1e-9) max = min + 1;
9429
+ const step = w / (list.length - 1);
9430
+ const pts = vals
9431
+ .map((v, i) => (i * step).toFixed(2) + ',' + (h - 2 - ((v - min) / (max - min)) * (h - 4)).toFixed(2))
9432
+ .join(' ');
9433
+ const up = vals[vals.length - 1] >= vals[0];
9434
+ return '<svg viewBox="0 0 ' + w + ' ' + h + '" preserveAspectRatio="none" class="' + (up ? 'up' : 'down') + '">' +
9435
+ '<polyline points="' + pts + '" fill="none" stroke="currentColor" stroke-width="1.4" vector-effect="non-scaling-stroke" /></svg>';
9436
+ }
9437
+
9438
+ function finRenderStats(stats, equity, cur) {
9439
+ const s = stats || null;
9440
+ if (els.finEquityBox) {
9441
+ els.finEquityBox.innerHTML = finSparkline(equity);
9442
+ }
9443
+ if (els.finStatsInfo) {
9444
+ els.finStatsInfo.textContent = s && s.at
9445
+ ? '· son 90 gün · ' + new Date(s.at).toLocaleTimeString('tr-TR', { hour: '2-digit', minute: '2-digit' })
9446
+ : '';
9447
+ }
9448
+ if (!s) {
9449
+ finKvSet(els.finStatToday, '—');
9450
+ finKvSet(els.finStatWeek, '—');
9451
+ finKvSet(els.finStatWin, '—');
9452
+ finKvSet(els.finStatPf, '—');
9453
+ finKvSet(els.finStatDd, '—');
9454
+ finKvSet(els.finStatTrades, '—');
9455
+ return;
9456
+ }
9457
+ const byDay = s.byDay && typeof s.byDay === 'object' ? s.byDay : {};
9458
+ const today = Number(byDay[finDayKey(Date.now())]) || 0;
9459
+ const week = Object.entries(byDay)
9460
+ .filter(([d]) => Date.now() - new Date(d + 'T00:00:00').getTime() < 7 * 86400000)
9461
+ .reduce((a, [, v]) => a + (Number(v) || 0), 0);
9462
+ const fmt = (n) => (n > 0 ? '+' : '') + fmtMoney(n, cur);
9463
+ finKvSet(els.finStatToday, fmt(today), today !== 0 ? (today > 0 ? 'pos' : 'neg') : null);
9464
+ finKvSet(els.finStatWeek, fmt(week), week !== 0 ? (week > 0 ? 'pos' : 'neg') : null);
9465
+ finKvSet(els.finStatWin, '%' + (Number(s.winRate) || 0) + ' (' + (Number(s.wins) || 0) + '/' + (Number(s.losses) || 0) + ')');
9466
+ finKvSet(els.finStatPf, s.profitFactor == null ? '∞' : String(s.profitFactor));
9467
+ const dd = s.drawdown || {};
9468
+ finKvSet(els.finStatDd, '%' + (Number(dd.maxPct) || 0), (Number(dd.maxPct) || 0) > 0 ? 'neg' : null);
9469
+ finKvSet(els.finStatTrades, String(Number(s.trades) || 0));
9470
+ }
9471
+
9472
+ function finRenderAlerts(list) {
9473
+ const box = els.finAlertList;
9474
+ if (!box) return;
9475
+ box.textContent = '';
9476
+ if (!list || !list.length) {
9477
+ const d = document.createElement('div');
9478
+ d.className = 'fin-empty';
9479
+ d.textContent = 'Aktif fiyat alarmı yok';
9480
+ box.appendChild(d);
9481
+ return;
9482
+ }
9483
+ for (const a of list) {
9484
+ const row = document.createElement('div');
9485
+ row.className = 'fin-alert';
9486
+ row.innerHTML =
9487
+ '<span class="fa-sym">' + escapeHtml(String(a.symbol || '?')) + '</span>' +
9488
+ '<span class="fa-cond">' + (a.direction === 'below' ? '≤' : '≥') + ' ' + (Number(a.price) || 0) + '</span>' +
9489
+ (a.note ? '<span class="fa-note">' + escapeHtml(String(a.note)) + '</span>' : '');
9490
+ box.appendChild(row);
9491
+ }
9492
+ }
9493
+
9077
9494
  async function finFillModels() {
9078
9495
  if (!els.finTraderModel) return;
9079
9496
  let models = [];
@@ -9115,6 +9532,9 @@ async function finSnapshot() {
9115
9532
  finTraderInputsSet(r.cfg);
9116
9533
  finRenderRoles(r.cfg);
9117
9534
  finRenderTrader(r.trader, r.cfg);
9535
+ finRenderAutomation(r.cfg, r.watch);
9536
+ finRenderStats(r.stats, r.equity, r.account && r.account.currency ? r.account.currency : '');
9537
+ finRenderAlerts(r.alerts);
9118
9538
  }
9119
9539
 
9120
9540
  function finLogLine(line) {
@@ -9132,7 +9552,11 @@ function finLogLine(line) {
9132
9552
  function finOnEvent(ev) {
9133
9553
  if (!ev || ev.type !== 'finance') return;
9134
9554
  if (ev.fn === 'log' && ev.line) finLogLine(ev.line);
9135
- else if (ev.fn === 'trade' && ev.line) {
9555
+ else if (ev.fn === 'notify' && ev.line) {
9556
+ finLogLine(ev.line);
9557
+ toast(ev.line);
9558
+ finSnapshot();
9559
+ } else if (ev.fn === 'trade' && ev.line) {
9136
9560
  finLogLine(ev.line);
9137
9561
  toast(ev.line);
9138
9562
  finSnapshot();
@@ -9219,6 +9643,60 @@ if (els.finMaxLot) els.finMaxLot.addEventListener('change', () => {
9219
9643
  toast('Max lot: ' + v + ' — sonraki turdan itibaren geçerli');
9220
9644
  });
9221
9645
  if (els.finSymBtn) els.finSymBtn.addEventListener('click', finSymPickerOpen);
9646
+
9647
+ /* RİSK OTOMASYONU kontrolleri: değişiklik anında kaydedilir (ajan turunu beklemez) */
9648
+ if (els.finWatchdogCb) {
9649
+ els.finWatchdogCb.addEventListener('change', () => {
9650
+ finSaveCfg({ watchdog: els.finWatchdogCb.checked });
9651
+ toast('Risk otomasyonu ' + (els.finWatchdogCb.checked ? 'açık — BE/trailing/kısmi TP 5 sn döngüyle uygulanır' : 'kapalı'));
9652
+ });
9653
+ }
9654
+ const finAutoNum = (el, key, min, max, step) => {
9655
+ if (!el) return;
9656
+ el.addEventListener('change', () => {
9657
+ let v = Number(el.value);
9658
+ if (!isFinite(v)) v = min;
9659
+ v = Math.max(min, Math.min(max, step ? Math.round(v / step) * step : v));
9660
+ el.value = v;
9661
+ finSaveCfg({ [key]: v });
9662
+ });
9663
+ };
9664
+ finAutoNum(els.finBeOnR, 'beOnR', 0, 10);
9665
+ finAutoNum(els.finTrailStartR, 'trailStartR', 0, 10);
9666
+ finAutoNum(els.finTrailR, 'trailR', 0, 5);
9667
+ finAutoNum(els.finPartialR, 'partialR', 0, 10);
9668
+ finAutoNum(els.finPartialPct, 'partialPct', 0, 90, 5);
9669
+ finAutoNum(els.finMaxDailyLoss, 'maxDailyLossPct', 0, 50);
9670
+ finAutoNum(els.finRiskPct, 'riskPerTradePct', 0, 20, 0.1);
9671
+ finAutoNum(els.finMaxPerSymbol, 'maxPerSymbol', 0, 20, 1);
9672
+ finAutoNum(els.finMaxSameSide, 'maxSameSide', 0, 20, 1);
9673
+ finAutoNum(els.finMinMarginLevel, 'minMarginLevel', 0, 1000, 10);
9674
+ if (els.finDailyLossAction) {
9675
+ els.finDailyLossAction.addEventListener('change', () => {
9676
+ finSaveCfg({ dailyLossAction: els.finDailyLossAction.value });
9677
+ const t = els.finDailyLossAction.options[els.finDailyLossAction.selectedIndex].textContent;
9678
+ toast('Günlük limit aksiyonu: ' + t);
9679
+ });
9680
+ }
9681
+ if (els.finNotifyTarget) {
9682
+ els.finNotifyTarget.addEventListener('change', () => {
9683
+ finSaveCfg({ notifyTarget: els.finNotifyTarget.value });
9684
+ toast('Bildirim kanalı: ' + els.finNotifyTarget.options[els.finNotifyTarget.selectedIndex].textContent);
9685
+ });
9686
+ }
9687
+ if (els.finReportBtn) {
9688
+ els.finReportBtn.addEventListener('click', async () => {
9689
+ els.finReportBtn.disabled = true;
9690
+ els.finReportBtn.textContent = '⏳ Rapor…';
9691
+ const r = await beast.financeReport().catch((e) => ({ ok: false, error: String((e && e.message) || e) }));
9692
+ els.finReportBtn.disabled = false;
9693
+ els.finReportBtn.textContent = '📄 Rapor';
9694
+ if (r && r.ok) toast('Rapor hazır: ' + String(r.path || '').split(/[\\/]/).pop());
9695
+ else toast('Rapor üretilemedi: ' + ((r && r.error) || '?'));
9696
+ finSnapshot();
9697
+ });
9698
+ }
9699
+
9222
9700
  /* OTOMATİK İŞLEM onay kutusu KALDIRILDI — trade ajanı daima işlem açabilir */
9223
9701
  if (els.finTraderModel) {
9224
9702
  els.finTraderModel.addEventListener('change', () => {
@@ -1484,6 +1484,19 @@ body.browser-open .mini-overlay { right: var(--bw, 480px); }
1484
1484
  font-size: 12px;
1485
1485
  padding: 4px 6px;
1486
1486
  }
1487
+ /* ---------- İzleyici EKLEME formu ---------- */
1488
+ #wAddBox {
1489
+ display: flex; flex-direction: column; gap: 6px;
1490
+ background: var(--panel); border: 1px solid var(--border);
1491
+ border-radius: 9px; padding: 8px; margin-bottom: 10px;
1492
+ }
1493
+ .mini-add-row { display: flex; gap: 6px; align-items: center; }
1494
+ .mini-add-row .inp { flex: 1; min-width: 0; margin: 0; padding: 5px 8px; font-size: 12px; }
1495
+ #wAddBtn {
1496
+ flex: none; background: var(--accent); color: #fff; border: none;
1497
+ border-radius: 7px; padding: 5px 11px; font-size: 14px; cursor: pointer;
1498
+ }
1499
+ #wAddBtn:hover { filter: brightness(1.1); }
1487
1500
  .wl-row {
1488
1501
  display: flex; align-items: baseline; gap: 7px;
1489
1502
  background: var(--panel); border: 1px solid var(--border);
@@ -4832,6 +4845,29 @@ body.finance-mode #composerWrap { margin-right: var(--finW, 400px); }
4832
4845
  .fin-grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
4833
4846
  .fin-grid2 .fin-field { margin-bottom: 8px; }
4834
4847
  .fin-hint { font-style: normal; opacity: 0.75; }
4848
+ /* RİSK OTOMASYONU + PERFORMANS kartları */
4849
+ .fin-check label { display: flex; align-items: center; gap: 6px; font-size: 11px; color: var(--text); cursor: pointer; }
4850
+ .fin-check input { accent-color: var(--accent); margin: 0; }
4851
+ .fin-kv b.pos { color: #22c55e; }
4852
+ .fin-kv b.neg { color: #ef4444; }
4853
+ .fin-equity { height: 34px; margin-bottom: 8px; color: var(--muted); }
4854
+ .fin-equity svg { width: 100%; height: 100%; display: block; }
4855
+ .fin-equity svg.up { color: #22c55e; }
4856
+ .fin-equity svg.down { color: #ef4444; }
4857
+ .fin-alerts { margin-top: 6px; display: flex; flex-direction: column; gap: 4px; }
4858
+ .fin-alert {
4859
+ display: flex;
4860
+ align-items: center;
4861
+ gap: 6px;
4862
+ font-size: 11px;
4863
+ background: var(--panel2);
4864
+ border-radius: 4px;
4865
+ padding: 3px 6px;
4866
+ min-width: 0;
4867
+ }
4868
+ .fin-alert .fa-sym { font-weight: 700; }
4869
+ .fin-alert .fa-cond { color: #eab308; font-variant-numeric: tabular-nums; }
4870
+ .fin-alert .fa-note { color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
4835
4871
  /* ekib sayısı + otomatik atama özeti */
4836
4872
  .fin-team { display: flex; align-items: center; gap: 8px; min-width: 0; }
4837
4873
  .fin-team-hint {