beast-agent 2.14.0 → 2.16.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.
Files changed (41) hide show
  1. package/package.json +1 -1
  2. package/src/agent/engine.js +499 -154
  3. package/src/agent/financetools.js +307 -0
  4. package/src/agent/opencode/agents.js +161 -0
  5. package/src/agent/opencode/askperm.js +135 -0
  6. package/src/agent/opencode/index.js +51 -0
  7. package/src/agent/opencode/permission.js +108 -0
  8. package/src/agent/opencode/prompts/anthropic.txt +105 -0
  9. package/src/agent/opencode/prompts/beast.txt +147 -0
  10. package/src/agent/opencode/prompts/build-switch.txt +5 -0
  11. package/src/agent/opencode/prompts/codex.txt +79 -0
  12. package/src/agent/opencode/prompts/default.txt +95 -0
  13. package/src/agent/opencode/prompts/edit.txt +10 -0
  14. package/src/agent/opencode/prompts/explore.txt +18 -0
  15. package/src/agent/opencode/prompts/gemini.txt +155 -0
  16. package/src/agent/opencode/prompts/glob.txt +6 -0
  17. package/src/agent/opencode/prompts/gpt.txt +107 -0
  18. package/src/agent/opencode/prompts/grep.txt +8 -0
  19. package/src/agent/opencode/prompts/kimi.txt +95 -0
  20. package/src/agent/opencode/prompts/max-steps.txt +16 -0
  21. package/src/agent/opencode/prompts/meta.txt +65 -0
  22. package/src/agent/opencode/prompts/plan.txt +26 -0
  23. package/src/agent/opencode/prompts/read.txt +14 -0
  24. package/src/agent/opencode/prompts/skill.txt +5 -0
  25. package/src/agent/opencode/prompts/task.txt +19 -0
  26. package/src/agent/opencode/prompts/todowrite.txt +44 -0
  27. package/src/agent/opencode/prompts/trinity.txt +97 -0
  28. package/src/agent/opencode/prompts/webfetch.txt +13 -0
  29. package/src/agent/opencode/prompts/websearch.txt +14 -0
  30. package/src/agent/opencode/prompts/write.txt +8 -0
  31. package/src/agent/opencode/systemprompt.js +157 -0
  32. package/src/agent/opencode/toolmap.js +316 -0
  33. package/src/agent/scripts/mt5_bridge.py +381 -0
  34. package/src/agent/tools.js +10 -3
  35. package/src/main.js +503 -69
  36. package/src/mt5bridge.js +215 -0
  37. package/src/preload.js +13 -3
  38. package/src/renderer/index.html +67 -0
  39. package/src/renderer/renderer.js +552 -53
  40. package/src/renderer/style.css +328 -1
  41. package/src/agent/android.js +0 -265
@@ -1,4 +1,4 @@
1
- 'use strict';
1
+ 'use strict';
2
2
 
3
3
  /* Beast renderer — vanilla JS, event-driven via window.beast IPC bridge. */
4
4
 
@@ -128,6 +128,33 @@ const els = {
128
128
  stMic: $('#stMic'),
129
129
  studioRow: $('#studioRow'),
130
130
  stSplit: $('#stSplit'),
131
+ finBtn: $('#finBtn'),
132
+ finPanel: $('#finPanel'),
133
+ finDot: $('#finDot'),
134
+ finTerm: $('#finTerm'),
135
+ finReconnect: $('#finReconnect'),
136
+ finBalance: $('#finBalance'),
137
+ finEquity: $('#finEquity'),
138
+ finProfit: $('#finProfit'),
139
+ finFree: $('#finFree'),
140
+ finTraderModel: $('#finTraderModel'),
141
+ finInterval: $('#finInterval'),
142
+ finMaxLot: $('#finMaxLot'),
143
+ finSymBtn: $('#finSymBtn'),
144
+ finAllow: $('#finAllow'),
145
+ finTraderBtn: $('#finTraderBtn'),
146
+ finTraderDot: $('#finTraderDot'),
147
+ finTraderStatus: $('#finTraderStatus'),
148
+ finPosList: $('#finPosList'),
149
+ finPosCount: $('#finPosCount'),
150
+ finSymList: $('#finSymList'),
151
+ finLog: $('#finLog'),
152
+ finSymAdd: $('#finSymAdd'),
153
+ finSymOverlay: $('#finSymOverlay'),
154
+ finSymClose: $('#finSymClose'),
155
+ finSymSearch: $('#finSymSearch'),
156
+ finSymPick: $('#finSymPick'),
157
+ finSymCount: $('#finSymCount'),
131
158
  stVideoEl: $('#stVideoEl'),
132
159
  stVideoName: $('#stVideoName'),
133
160
  stVideoOpen: $('#stVideoOpen'),
@@ -735,7 +762,7 @@ function closeChatToolGroup() {
735
762
  function argSummary(name, args) {
736
763
  try {
737
764
  if (name === 'run_command') return String(args.command || '');
738
- if (name === 'read_file' || name === 'write_file' || name === 'edit_file') return String(args.path || '');
765
+ if (name === 'read_file' || name === 'write_file' || name === 'edit_file' || name === 'read' || name === 'write' || name === 'edit') return String(args.path || args.filePath || '');
739
766
  if (name === 'grep') return String(args.pattern || '');
740
767
  if (name === 'glob') return String(args.pattern || '');
741
768
  if (name === 'list_dir') return String(args.path || '.');
@@ -879,6 +906,9 @@ async function renderSessions(list) {
879
906
  tgSet = new Set(await beast.tgListSessions());
880
907
  } catch {}
881
908
  els.sessList.innerHTML = '';
909
+ /* BEAST FINANCE izolasyonu: finance modunda YALNIZ finance sohbetleri,
910
+ normal modda finance olmayanlar listelenir */
911
+ list = (list || []).filter((s) => (financeModeOn() ? !!s.finance : !s.finance));
882
912
  /* elle sıra: sessionOrder'daki id'ler önde (o sırayla), diğerleri engine sırasıyla arkada */
883
913
  const rank = new Map(sessionOrder.map((id, i) => [id, i]));
884
914
  const withRank = list.map((s, i) => ({ s, r: rank.has(s.id) ? rank.get(s.id) : 1e6 + i }));
@@ -977,6 +1007,8 @@ async function openSession(id) {
977
1007
  els.msgs.innerHTML = '';
978
1008
  showEmpty(s.messages.length === 0);
979
1009
  renderTodos(s.todos || []);
1010
+ /* finance modu açıkken oturum değişimi: yeni oturumu da finance bayrağıyla işaretle */
1011
+ if (financeModeOn()) beast.financeMode(true, id).catch(() => {});
980
1012
 
981
1013
  for (let i = 0; i < s.messages.length; i++) {
982
1014
  const m = s.messages[i];
@@ -2379,9 +2411,6 @@ async function renderInstallPane() {
2379
2411
  (r.canToggle
2380
2412
  ? '<button class="btn hr-tgl" data-on="' + (r.toggleOn ? '1' : '0') + '" style="width:auto;padding:2px 12px;flex:none">' + (r.toggleOn ? 'KAPAT' : 'AÇ') + '</button>'
2381
2413
  : '') +
2382
- (r.canInstall
2383
- ? '<button class="btn inst-kur" style="width:auto;padding:2px 12px;flex:none">KUR</button>'
2384
- : '') +
2385
2414
  badge(r.state) +
2386
2415
  pctLabel(r) +
2387
2416
  '</div>'
@@ -2397,16 +2426,6 @@ async function renderInstallPane() {
2397
2426
  refresh();
2398
2427
  });
2399
2428
  });
2400
- /* Android emülatörü: KUR → arka plan SDK kurulumu (JDK + cmdline-tools + imaj) */
2401
- el.querySelectorAll('.inst-kur').forEach((b) => {
2402
- b.addEventListener('click', async () => {
2403
- b.disabled = true;
2404
- b.textContent = '…';
2405
- await beast.androidInstall().catch(() => {});
2406
- toast('Android SDK arka planda kuruluyor — ~1.5GB, internete göre 5-20 dk sürer');
2407
- refresh();
2408
- });
2409
- });
2410
2429
  };
2411
2430
 
2412
2431
  const refresh = async () => {
@@ -3107,8 +3126,39 @@ async function renderUpdatePane(autoCheck) {
3107
3126
  if (autoCheck) beast.updateCheck().catch(() => {});
3108
3127
  }
3109
3128
 
3110
- /* Sohbete onay kartı düşürür — Onayla / Her zaman / Reddet */function showApprovalCard(ev) {
3111
- streamEl = null;
3129
+ /* opencode permission kartı (BC): izin isteği — Bir kez / Her zaman / Reddet.
3130
+ ev: {id, permission, patterns, metadata, always, tool} */
3131
+ function showPermissionCard(req, inBc) {
3132
+ const toolName = (req.tool && req.tool.name) || req.permission || 'izin';
3133
+ let preview = '';
3134
+ try {
3135
+ preview = JSON.stringify(req.metadata || {}).slice(0, 200);
3136
+ } catch {}
3137
+ const card = document.createElement('div');
3138
+ card.className = 'tool-card';
3139
+ card.innerHTML =
3140
+ `<div class="tool-head"><span class="tool-icon"><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="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg></span>` +
3141
+ `<span class="tool-name">${escapeHtml(String(req.permission || 'izin'))}</span>` +
3142
+ `<span class="tool-arg">${escapeHtml(String(Array.isArray(req.patterns) ? req.patterns.join(', ') : '*').slice(0, 160))}</span></div>` +
3143
+ `<div class="tool-body open" style="display:flex;gap:8px;align-items:center">` +
3144
+ `<button class="btn ap-ok">${_t('sec_ok')}</button>` +
3145
+ `<button class="btn ghost ap-always">${_t('sec_always')}</button>` +
3146
+ `<button class="btn ghost ap-no">${_t('sec_no')}</button>` +
3147
+ `</div>`;
3148
+ const done = (txt) => {
3149
+ card.querySelector('.tool-body').innerHTML = `<span class="sub">${escapeHtml(txt)}</span>`;
3150
+ };
3151
+ card.querySelector('.ap-ok').addEventListener('click', async () => { await beast.permissionReply(req.id, 'once'); done(_t('sec_ok_done')); });
3152
+ card.querySelector('.ap-always').addEventListener('click', async () => { await beast.permissionReply(req.id, 'always'); done(_t('sec_always_done')); });
3153
+ card.querySelector('.ap-no').addEventListener('click', async () => { await beast.permissionReply(req.id, 'reject'); done(_t('sec_no_done')); });
3154
+ if (inBc && typeof bcLine === 'function') {
3155
+ bcLine('t-dim', '⏳ izin bekleniyor: ' + toolName + (preview ? ' — ' + preview : ''));
3156
+ }
3157
+ els.msgs.appendChild(card);
3158
+ scrollDown(true);
3159
+ }
3160
+
3161
+ /* Sohbete onay kartı düşürür — Onayla / Her zaman / Reddet */function showApprovalCard(ev) { streamEl = null;
3112
3162
  const card = document.createElement('div');
3113
3163
  card.className = 'tool-card';
3114
3164
  card.innerHTML =
@@ -5042,6 +5092,15 @@ function onEvent(ev) {
5042
5092
  return;
5043
5093
  }
5044
5094
  if (ev.type === 'install-progress') { updateInstallPct(ev); return; }
5095
+ /* opencode permission (BC): izin kartı — Bir kez / Her zaman / Reddet.
5096
+ BC oturumunda kart IDE panel akışına düşer; diğerlerinde sohbete */
5097
+ if (ev.type === 'permission.asked') {
5098
+ const req = ev.request || {};
5099
+ const isBc = bcSessionId && ev.sessionId === bcSessionId;
5100
+ if (!isBc && ev.sessionId && ev.sessionId !== activeId) return;
5101
+ showPermissionCard(req, isBc);
5102
+ return;
5103
+ }
5045
5104
  /* Headroom durum değişimi → açık olan sekmeyi tazele (Token Sıkıştırma / Kurulum) */
5046
5105
  if (ev.type === 'headroom-status') {
5047
5106
  try {
@@ -5168,7 +5227,7 @@ function onEvent(ev) {
5168
5227
  setStatus(ev.name + '…');
5169
5228
  termAgentEvent(ev);
5170
5229
  /* ajan dosya yazarsa/editlerse açık IDE sekmesini izlemeye al */
5171
- if ((ev.name === 'write_file' || ev.name === 'edit_file') && ev.args && ev.args.path) codeWriteWatch.set(ev.callId, ev.args.path);
5230
+ if ((ev.name === 'write_file' || ev.name === 'edit_file' || ev.name === 'write' || ev.name === 'edit') && ev.args && ev.args.path) codeWriteWatch.set(ev.callId, ev.args.path);
5172
5231
  break;
5173
5232
  case 'tool-end':
5174
5233
  finishToolCard(ev.callId, ev.ok, ev.result, ev.diff);
@@ -6089,6 +6148,11 @@ async function init() {
6089
6148
  showEmpty(true);
6090
6149
  }
6091
6150
 
6151
+ /* BEAST FINANCE: kapanışta mod açıktıysa geri yükle */
6152
+ try {
6153
+ if (localStorage.getItem('beast.financeMode') === '1') await setFinanceMode(true);
6154
+ } catch {}
6155
+
6092
6156
  els.newChat.addEventListener('click', async () => {
6093
6157
  try {
6094
6158
  const created = await beast.createSession();
@@ -7245,6 +7309,7 @@ function ideModeOn() {
7245
7309
  async function setIdeMode(on) {
7246
7310
  if (on && studioModeOn()) await setStudioMode(false); /* Studio açıkken IDE'ye geçiş — Studio kapanır */
7247
7311
  if (on && sbModeOn()) setSandboxMode(false); /* Sandbox açıkken IDE'ye geçiş — Sandbox kapanır */
7312
+ if (on && financeModeOn()) setFinanceMode(false); /* Finance açıkken IDE'ye geçiş — Finance kapanır */
7248
7313
  document.body.classList.toggle('ide-mode', !!on);
7249
7314
  if (els.ideBtn) els.ideBtn.classList.toggle('on', !!on);
7250
7315
  /* soldaki marka: chat modunda BEAST Agent, IDE modunda BEAST Code, Studio modunda BEAST Studio */
@@ -7319,6 +7384,7 @@ function studioModeOn() {
7319
7384
  async function setStudioMode(on) {
7320
7385
  if (on && ideModeOn()) await setIdeMode(false); /* IDE açıkken Studio'ya geçiş — IDE kapanır */
7321
7386
  if (on && sbModeOn()) setSandboxMode(false); /* Sandbox açıkken Studio'ya geçiş — Sandbox kapanır */
7387
+ if (on && financeModeOn()) setFinanceMode(false); /* Finance açıkken Studio'ya geçiş — Finance kapanır */
7322
7388
  document.body.classList.toggle('studio-mode', !!on);
7323
7389
  if (els.studioBtn) els.studioBtn.classList.toggle('on', !!on);
7324
7390
  /* ALT TERMINAL: yalnız Beast Code'a özgü — Studio'ya geçişte kapanır */
@@ -8049,6 +8115,450 @@ async function codeReloadIfOpen(p) {
8049
8115
 
8050
8116
  $('#ideBtn').addEventListener('click', () => setIdeMode(!ideModeOn()));
8051
8117
  if (els.studioBtn) els.studioBtn.addEventListener('click', () => setStudioMode(!studioModeOn()));
8118
+
8119
+ /* ================= BEAST FINANCE MODU =================
8120
+ Sol sidebar (sohbet geçmişi + izleyiciler) ve chat AYNI kalır; sağda MT5
8121
+ işlem paneli: hesap, pozisyonlar, piyasa, trade ajanı kontrolleri, akış. */
8122
+
8123
+ function financeModeOn() {
8124
+ return document.body.classList.contains('finance-mode');
8125
+ }
8126
+
8127
+ let finPollTimer = null;
8128
+ let finModelsFilled = false;
8129
+ let finLastPrices = new Map(); /* sembol → son bid (renk için) */
8130
+ const FIN_COLOR_UP = 'fs-up';
8131
+ const FIN_COLOR_DOWN = 'fs-down';
8132
+
8133
+ function fmtMoney(v, cur) {
8134
+ const n = Number(v);
8135
+ if (!isFinite(n)) return '—';
8136
+ const s = n.toLocaleString('tr-TR', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
8137
+ return cur ? s + ' ' + cur : s;
8138
+ }
8139
+
8140
+ function finSetDot(state, bridge) {
8141
+ if (!els.finDot) return;
8142
+ els.finDot.classList.remove('on', 'off', 'busy');
8143
+ if (state === true) els.finDot.classList.add('on');
8144
+ else if (state === 'busy') els.finDot.classList.add('busy');
8145
+ else els.finDot.classList.add('off');
8146
+ if (els.finTerm) {
8147
+ let label = 'MT5';
8148
+ if (bridge) {
8149
+ if (bridge.terminal) label = (bridge.terminal.name || 'MT5') + (bridge.terminal.company ? ' · ' + bridge.terminal.company : '');
8150
+ else if (bridge.error) label = bridge.error.length > 60 ? bridge.error.slice(0, 60) + '…' : bridge.error;
8151
+ else if (bridge.running) label = 'MT5 terminali bekleniyor…';
8152
+ else label = 'Köprü kapalı';
8153
+ }
8154
+ els.finTerm.textContent = label;
8155
+ els.finTerm.title = bridge && bridge.error ? bridge.error : label;
8156
+ }
8157
+ }
8158
+
8159
+ function finKvSet(el, text, cls) {
8160
+ if (!el) return;
8161
+ el.textContent = text;
8162
+ el.classList.remove('pos', 'neg');
8163
+ if (cls) el.classList.add(cls);
8164
+ }
8165
+
8166
+ function finRenderAccount(acc) {
8167
+ const cur = acc && acc.currency ? acc.currency : '';
8168
+ finKvSet(els.finBalance, acc ? fmtMoney(acc.balance, cur) : '—');
8169
+ finKvSet(els.finEquity, acc ? fmtMoney(acc.equity, cur) : '—');
8170
+ const p = acc ? Number(acc.profit) || 0 : NaN;
8171
+ finKvSet(els.finProfit, isFinite(p) ? (p > 0 ? '+' : '') + fmtMoney(p, cur) : '—', isFinite(p) && p !== 0 ? (p > 0 ? 'pos' : 'neg') : null);
8172
+ finKvSet(els.finFree, acc ? fmtMoney(acc.margin_free, cur) : '—');
8173
+ }
8174
+
8175
+ function finRenderPositions(list) {
8176
+ if (!els.finPosList) return;
8177
+ els.finPosList.textContent = '';
8178
+ if (els.finPosCount) els.finPosCount.textContent = list && list.length ? '(' + list.length + ')' : '';
8179
+ if (!list || !list.length) {
8180
+ const d = document.createElement('div');
8181
+ d.className = 'fin-empty';
8182
+ d.textContent = 'Açık pozisyon yok';
8183
+ els.finPosList.appendChild(d);
8184
+ return;
8185
+ }
8186
+ for (const p of list) {
8187
+ const row = document.createElement('div');
8188
+ row.className = 'fin-pos';
8189
+ const isBuy = String(p.type) === '0' || Number(p.type) === 0;
8190
+ const profit = Number(p.profit) || 0;
8191
+ row.innerHTML =
8192
+ '<span class="fp-sym" title="ticket ' + p.ticket + '">' + (p.symbol || '?') + '</span>' +
8193
+ '<span class="fp-dir ' + (isBuy ? 'buy">BUY' : 'sell">SELL') + '</span>' +
8194
+ '<span class="fp-meta">' + (Number(p.volume) || 0) + ' lot · ' + (Number(p.price_open) || 0) + ' → ' + (Number(p.price_current) || 0) +
8195
+ (p.sl ? ' · SL ' + p.sl : '') + (p.tp ? ' · TP ' + p.tp : '') + '</span>' +
8196
+ '<span class="fp-pl ' + (profit >= 0 ? 'pos' : 'neg') + '">' + (profit >= 0 ? '+' : '') + profit.toFixed(2) + '</span>';
8197
+ const btn = document.createElement('button');
8198
+ btn.className = 'fp-close';
8199
+ btn.title = 'Pozisyonu kapat';
8200
+ btn.textContent = '✕';
8201
+ btn.addEventListener('click', async () => {
8202
+ const ok = await uiConfirm((p.symbol || '') + ' pozisyonu kapatılsın mı? (ticket ' + p.ticket + ')', 'Kapat');
8203
+ if (!ok) return;
8204
+ const r = await beast.financeClose(p.ticket).catch((e) => ({ ok: false, error: String((e && e.message) || e) }));
8205
+ if (r && r.ok) toast('Pozisyon kapatıldı');
8206
+ else toast('Kapatılamadı: ' + ((r && r.error) || '?'));
8207
+ finSnapshot();
8208
+ });
8209
+ row.appendChild(btn);
8210
+ els.finPosList.appendChild(row);
8211
+ }
8212
+ }
8213
+
8214
+ function finRenderSymbols(list) {
8215
+ if (!els.finSymList) return;
8216
+ els.finSymList.textContent = '';
8217
+ if (!list || !list.length) {
8218
+ const d = document.createElement('div');
8219
+ d.className = 'fin-empty';
8220
+ d.textContent = 'Sembol verisi yok';
8221
+ els.finSymList.appendChild(d);
8222
+ return;
8223
+ }
8224
+ for (const s of list) {
8225
+ if (s && s.missing) continue;
8226
+ const row = document.createElement('div');
8227
+ row.className = 'fin-sym';
8228
+ const bid = Number(s.bid) || 0;
8229
+ const prev = finLastPrices.get(s.symbol);
8230
+ let cls = '';
8231
+ if (prev != null && bid !== prev) cls = bid > prev ? FIN_COLOR_UP : FIN_COLOR_DOWN;
8232
+ if (bid) finLastPrices.set(s.symbol, bid);
8233
+ const digits = s.digits != null ? Number(s.digits) : 5;
8234
+ row.innerHTML =
8235
+ '<span class="fs-name">' + (s.symbol || '?') + '</span>' +
8236
+ '<span class="fs-price ' + cls + '">' + bid.toFixed(digits) + '</span>' +
8237
+ '<span class="fs-spread">sp ' + (s.spread != null ? s.spread : '—') + '</span>';
8238
+ els.finSymList.appendChild(row);
8239
+ }
8240
+ }
8241
+
8242
+ function finSymBtnLabel(list) {
8243
+ const l = Array.isArray(list) ? list : [];
8244
+ if (!l.length) return 'Seç…';
8245
+ const head = l.slice(0, 3).join(', ');
8246
+ return l.length > 3 ? head + ' +' + (l.length - 3) : head;
8247
+ }
8248
+
8249
+ function finSymBtnUpdate() {
8250
+ if (els.finSymBtn) {
8251
+ els.finSymBtn.textContent = finSymBtnLabel(finWatch);
8252
+ els.finSymBtn.title = finWatch.length
8253
+ ? 'İzleme listesi: ' + finWatch.join(', ') + ' — değiştirmek için tıkla'
8254
+ : 'MT5 sembol listesinden seç — tıkla';
8255
+ }
8256
+ }
8257
+
8258
+ function finTraderInputsSet(cfg) {
8259
+ if (!cfg) return;
8260
+ if (!finWatchDirty) finWatchSet(cfg.symbols);
8261
+ const ae = document.activeElement;
8262
+ if (els.finInterval && ae !== els.finInterval) els.finInterval.value = cfg.intervalSec || 120;
8263
+ if (els.finMaxLot && ae !== els.finMaxLot) els.finMaxLot.value = cfg.maxLot || 0.1;
8264
+ finSymBtnUpdate();
8265
+ if (els.finAllow && ae !== els.finAllow) els.finAllow.checked = cfg.allowTrading === true;
8266
+ if (els.finTraderModel && ae !== els.finTraderModel && cfg.traderSel) els.finTraderModel.value = cfg.traderSel;
8267
+ }
8268
+
8269
+ function finRenderTrader(trader, cfg) {
8270
+ if (!els.finTraderBtn) return;
8271
+ const on = !!(trader && trader.on);
8272
+ els.finTraderBtn.classList.toggle('stop', on);
8273
+ els.finTraderBtn.innerHTML = on ? '■ Durdur' : '▶ Ajanı Başlat';
8274
+ if (els.finTraderDot) {
8275
+ els.finTraderDot.classList.remove('on', 'off', 'busy');
8276
+ if (on) els.finTraderDot.classList.add(trader.busy ? 'busy' : 'on');
8277
+ else els.finTraderDot.classList.add('off');
8278
+ }
8279
+ if (els.finTraderStatus) {
8280
+ let t = '';
8281
+ if (trader && trader.lastAt) {
8282
+ const d = new Date(trader.lastAt);
8283
+ t = ' · son tur ' + String(d.getHours()).padStart(2, '0') + ':' + String(d.getMinutes()).padStart(2, '0');
8284
+ }
8285
+ const mode = cfg && cfg.allowTrading ? 'OTOMATİK' : 'analiz modu';
8286
+ els.finTraderStatus.textContent = on
8287
+ ? (trader.busy ? 'Tur çalışıyor…' : 'Beklemede — sıradaki tur ' + (cfg && cfg.intervalSec ? cfg.intervalSec + ' sn' : '')) + ' · tur #' + (trader.rounds || 0) + ' · ' + mode + t
8288
+ : 'Kapalı · ' + mode;
8289
+ }
8290
+ }
8291
+
8292
+ async function finFillModels() {
8293
+ if (!els.finTraderModel) return;
8294
+ let models = [];
8295
+ try {
8296
+ const st = await beast.getState();
8297
+ models = (st && st.models) || [];
8298
+ } catch {}
8299
+ els.finTraderModel.textContent = '';
8300
+ const optAuto = document.createElement('option');
8301
+ optAuto.value = '';
8302
+ optAuto.textContent = 'Genel aktif model';
8303
+ els.finTraderModel.appendChild(optAuto);
8304
+ for (const m of models) {
8305
+ const o = document.createElement('option');
8306
+ o.value = m.sel;
8307
+ o.textContent = (m.providerName || m.sel) + ' · ' + m.model;
8308
+ els.finTraderModel.appendChild(o);
8309
+ }
8310
+ finModelsFilled = true;
8311
+ }
8312
+
8313
+ async function finSnapshot() {
8314
+ if (!financeModeOn()) return;
8315
+ let r = null;
8316
+ try { r = await beast.financeSnapshot(); } catch {}
8317
+ if (!r || !r.ok) {
8318
+ finSetDot(false);
8319
+ return;
8320
+ }
8321
+ const b = r.bridge || {};
8322
+ finSetDot(b.connected ? true : b.running ? 'busy' : false, b);
8323
+ finRenderAccount(r.account);
8324
+ finRenderPositions(r.positions || []);
8325
+ finRenderSymbols(r.symbols || []);
8326
+ finTraderInputsSet(r.cfg);
8327
+ finRenderTrader(r.trader, r.cfg);
8328
+ }
8329
+
8330
+ function finLogLine(line) {
8331
+ if (!els.finLog) return;
8332
+ const d = document.createElement('div');
8333
+ d.className = 'fin-log-new';
8334
+ const now = new Date();
8335
+ d.textContent = String(now.getHours()).padStart(2, '0') + ':' + String(now.getMinutes()).padStart(2, '0') + ':' + String(now.getSeconds()).padStart(2, '0') + ' ' + line;
8336
+ els.finLog.insertBefore(d, els.finLog.firstChild);
8337
+ for (const c of els.finLog.children) c.classList.remove('fin-log-new');
8338
+ d.classList.add('fin-log-new');
8339
+ while (els.finLog.children.length > 200) els.finLog.removeChild(els.finLog.lastChild);
8340
+ }
8341
+
8342
+ function finOnEvent(ev) {
8343
+ if (!ev || ev.type !== 'finance') return;
8344
+ if (ev.fn === 'log' && ev.line) finLogLine(ev.line);
8345
+ else if (ev.fn === 'trade' && ev.line) {
8346
+ finLogLine(ev.line);
8347
+ toast(ev.line);
8348
+ finSnapshot();
8349
+ } else if (ev.fn === 'bridge') {
8350
+ finSetDot(ev.connected ? true : 'busy', { error: ev.error });
8351
+ } else if (ev.fn === 'trader') {
8352
+ if (ev.state === 'running') finLogLine('[trader] tur #' + (ev.round || 0) + ' başladı');
8353
+ else if (ev.state === 'idle') finLogLine('[trader] tur #' + (ev.round || 0) + ' bitti — sıradaki tur ~' + (ev.nextInSec || '?') + ' sn');
8354
+ else if (ev.state === 'stopped') finLogLine('[trader] durduruldu');
8355
+ } else if (ev.fn === 'install') {
8356
+ finLogLine('[MT5] paket kurulumu tamamlandı (kod ' + ev.code + ')');
8357
+ }
8358
+ }
8359
+ beast.onEvent(finOnEvent);
8360
+
8361
+ async function setFinanceMode(on) {
8362
+ if (on && ideModeOn()) await setIdeMode(false);
8363
+ if (on && studioModeOn()) await setStudioMode(false);
8364
+ if (on && sbModeOn()) setSandboxMode(false);
8365
+ document.body.classList.toggle('finance-mode', !!on);
8366
+ if (els.finBtn) els.finBtn.classList.toggle('on', !!on);
8367
+ const brandSub = document.querySelector('#brand .brand-sub');
8368
+ if (brandSub) brandSub.textContent = on ? 'Finance' : 'Agent';
8369
+ try { localStorage.setItem('beast.financeMode', on ? '1' : '0'); } catch {}
8370
+ beast.financeMode(!!on, activeId).catch(() => {});
8371
+ /* sohbet geçmişi listesi değişir: finance modunda yalnız finance sohbetleri */
8372
+ try {
8373
+ await refreshSessions();
8374
+ const rows = [...els.sessList.querySelectorAll('.sess')];
8375
+ const activeInList = activeId && rows.some((r) => r.dataset.sid === activeId);
8376
+ if (!activeInList && rows.length && rows[0].dataset.sid) {
8377
+ await openSession(rows[0].dataset.sid); /* en üstteki uygun sohbeti aç */
8378
+ }
8379
+ } catch {}
8380
+ if (on) {
8381
+ if (!finModelsFilled) await finFillModels();
8382
+ finSnapshot();
8383
+ if (!finPollTimer) finPollTimer = setInterval(finSnapshot, 3000);
8384
+ } else if (finPollTimer) {
8385
+ clearInterval(finPollTimer);
8386
+ finPollTimer = null;
8387
+ }
8388
+ }
8389
+
8390
+ if (els.finBtn) {
8391
+ els.finBtn.addEventListener('click', () => setFinanceMode(!financeModeOn()).catch(() => {}));
8392
+ }
8393
+ if (els.finReconnect) {
8394
+ els.finReconnect.addEventListener('click', async () => {
8395
+ toast('MT5 köprüsü yeniden başlatılıyor…');
8396
+ await beast.financeConnect().catch(() => {});
8397
+ finSnapshot();
8398
+ });
8399
+ }
8400
+
8401
+ /* trader kontrolleri: ayarlar anında kaydedilir (debounce) */
8402
+ let finCfgTimer = null;
8403
+ let finWatchDirty = false; /* picker/input değişikliği kaydedilmeyi bekliyor */
8404
+ function finSaveCfg(patch) {
8405
+ if (patch && patch.symbols !== undefined) finWatchDirty = true;
8406
+ clearTimeout(finCfgTimer);
8407
+ finCfgTimer = setTimeout(() => {
8408
+ beast.financeSettings(patch).then(() => { finWatchDirty = false; }).catch(() => { finWatchDirty = false; });
8409
+ }, 400);
8410
+ }
8411
+ if (els.finInterval) els.finInterval.addEventListener('change', () => finSaveCfg({ intervalSec: Number(els.finInterval.value) || 120 }));
8412
+ if (els.finMaxLot) els.finMaxLot.addEventListener('change', () => finSaveCfg({ maxLot: Number(els.finMaxLot.value) || 0.1 }));
8413
+ if (els.finSymBtn) els.finSymBtn.addEventListener('click', finSymPickerOpen);
8414
+ if (els.finAllow) {
8415
+ els.finAllow.addEventListener('change', async () => {
8416
+ if (els.finAllow.checked) {
8417
+ const ok = await uiConfirm(
8418
+ 'OTOMATİK İŞLEM açılıyor: trade ajanı GERÇEK hesapta emir açabilir/kapatabilir (max lot + pozisyon limitleriyle). Onaylıyor musun?',
8419
+ 'Aç',
8420
+ 'Vazgeç'
8421
+ );
8422
+ if (!ok) {
8423
+ els.finAllow.checked = false;
8424
+ return;
8425
+ }
8426
+ }
8427
+ finSaveCfg({ allowTrading: els.finAllow.checked });
8428
+ });
8429
+ }
8430
+ if (els.finTraderModel) {
8431
+ els.finTraderModel.addEventListener('change', () => {
8432
+ finSaveCfg({ traderSel: els.finTraderModel.value });
8433
+ toast('Trade ajanı modeli: ' + (els.finTraderModel.value || 'genel aktif model'));
8434
+ });
8435
+ }
8436
+ if (els.finTraderBtn) {
8437
+ els.finTraderBtn.addEventListener('click', async () => {
8438
+ if (els.finTraderBtn.classList.contains('stop')) {
8439
+ await beast.financeTraderStop().catch(() => {});
8440
+ } else {
8441
+ const r = await beast.financeTraderStart().catch((e) => ({ ok: false, error: String((e && e.message) || e) }));
8442
+ if (r && !r.ok) toast(r.error || 'Trader başlatılamadı');
8443
+ else if (r && r.ok) toast('Trade ajanı başladı — turlar panelde izlenir');
8444
+ }
8445
+ finSnapshot();
8446
+ });
8447
+ }
8448
+
8449
+ /* ---------- PİYASA sembol seçici ----------
8450
+ MT5 terminalindeki tüm semboller listelenir; tıkla → izleme listesine
8451
+ ekle/çıkar. PİYASA kartı yalnız seçili sembolleri gösterir. */
8452
+
8453
+ let finWatch = []; /* anlık izleme listesi (snapshot cfg'den senkron) */
8454
+ let finSymSearchTimer = null;
8455
+ let finSymRefreshTimer = null;
8456
+
8457
+ function finWatchSet(list) {
8458
+ finWatch = (Array.isArray(list) ? list : []).map((s) => String(s).trim().toUpperCase()).filter(Boolean);
8459
+ }
8460
+
8461
+ function finToggleWatch(sym) {
8462
+ const s = String(sym || '').toUpperCase();
8463
+ const i = finWatch.indexOf(s);
8464
+ if (i >= 0) finWatch.splice(i, 1);
8465
+ else finWatch.push(s);
8466
+ finSaveCfg({ symbols: [...finWatch] });
8467
+ finSymBtnUpdate(); /* TRADE AJANI düğmesi anında güncellensin */
8468
+ clearTimeout(finSymRefreshTimer);
8469
+ finSymRefreshTimer = setTimeout(() => finSnapshot(), 700); /* PİYASA kartı tazelensin */
8470
+ return i < 0; /* true = eklendi */
8471
+ }
8472
+
8473
+ function finRenderPickRows(rows, q) {
8474
+ if (!els.finSymPick) return;
8475
+ els.finSymPick.textContent = '';
8476
+ const term = String(q || '').trim().toUpperCase();
8477
+ const matches = rows.filter((r) => {
8478
+ if (!term) return true;
8479
+ return (String(r.name || '').toUpperCase().includes(term) || String(r.desc || '').toUpperCase().includes(term));
8480
+ });
8481
+ if (els.finSymCount) els.finSymCount.textContent = matches.length ? matches.length + ' sembol' : '';
8482
+ if (!matches.length) {
8483
+ const d = document.createElement('div');
8484
+ d.className = 'fin-empty';
8485
+ d.textContent = term ? 'Eşleşme yok' : 'Sembol listesi boş';
8486
+ els.finSymPick.appendChild(d);
8487
+ return;
8488
+ }
8489
+ const cap = 300;
8490
+ for (const r of matches.slice(0, cap)) {
8491
+ const row = document.createElement('div');
8492
+ row.className = 'fin-sym-row';
8493
+ const on = finWatch.includes(String(r.name || '').toUpperCase());
8494
+ row.innerHTML =
8495
+ '<span class="fsr-check">' + (on ? '✓' : '') + '</span>' +
8496
+ '<span class="fsr-name">' + escapeHtml(r.name || '?') + '</span>' +
8497
+ '<span class="fsr-desc">' + escapeHtml((r.desc || '').slice(0, 60)) + '</span>';
8498
+ if (on) row.classList.add('on');
8499
+ row.addEventListener('click', () => {
8500
+ const added = finToggleWatch(r.name);
8501
+ row.classList.toggle('on', added);
8502
+ row.querySelector('.fsr-check').textContent = added ? '✓' : '';
8503
+ });
8504
+ els.finSymPick.appendChild(row);
8505
+ }
8506
+ if (matches.length > cap) {
8507
+ const d = document.createElement('div');
8508
+ d.className = 'fin-empty';
8509
+ d.textContent = '…' + (matches.length - cap) + ' sembol daha — daraltmak için ara';
8510
+ els.finSymPick.appendChild(d);
8511
+ }
8512
+ }
8513
+
8514
+ async function finSymLoad(q) {
8515
+ if (!els.finSymPick) return;
8516
+ els.finSymPick.textContent = '';
8517
+ const d = document.createElement('div');
8518
+ d.className = 'fin-empty';
8519
+ d.textContent = 'MT5 sembolleri çekiliyor…';
8520
+ els.finSymPick.appendChild(d);
8521
+ const r = await beast.financeSymbolsList(q).catch(() => ({ ok: false, error: 'köprü yok' }));
8522
+ if (!r || !r.ok) {
8523
+ els.finSymPick.textContent = '';
8524
+ const e = document.createElement('div');
8525
+ e.className = 'fin-empty';
8526
+ e.textContent = (r && r.error) || 'MT5 sembolleri alınamadı';
8527
+ els.finSymPick.appendChild(e);
8528
+ if (els.finSymCount) els.finSymCount.textContent = '';
8529
+ return;
8530
+ }
8531
+ finRenderPickRows((r.data && r.data.symbols) || [], q);
8532
+ }
8533
+
8534
+ function finSymPickerOpen() {
8535
+ if (!els.finSymOverlay) return;
8536
+ els.finSymOverlay.hidden = false;
8537
+ if (els.finSymSearch) els.finSymSearch.value = '';
8538
+ finSymLoad('');
8539
+ if (els.finSymSearch) els.finSymSearch.focus();
8540
+ }
8541
+
8542
+ function finSymPickerClose() {
8543
+ if (els.finSymOverlay) els.finSymOverlay.hidden = true;
8544
+ }
8545
+
8546
+ if (els.finSymAdd) els.finSymAdd.addEventListener('click', finSymPickerOpen);
8547
+ if (els.finSymClose) els.finSymClose.addEventListener('click', finSymPickerClose);
8548
+ if (els.finSymOverlay) {
8549
+ els.finSymOverlay.addEventListener('click', (e) => {
8550
+ if (e.target === els.finSymOverlay) finSymPickerClose();
8551
+ });
8552
+ els.finSymOverlay.addEventListener('keydown', (e) => {
8553
+ if (e.key === 'Escape') { e.stopPropagation(); finSymPickerClose(); }
8554
+ });
8555
+ }
8556
+ if (els.finSymSearch) {
8557
+ els.finSymSearch.addEventListener('input', () => {
8558
+ clearTimeout(finSymSearchTimer);
8559
+ finSymSearchTimer = setTimeout(() => finSymLoad(els.finSymSearch.value.trim()), 350);
8560
+ });
8561
+ }
8052
8562
  $('#filePick').addEventListener('click', async () => {
8053
8563
  /* Studio modunda klasör seçimi Studio'nun KENDİ kökünü değiştirir —
8054
8564
  IDE köküne dokunmaz (dünyalar ayrıdır) */
@@ -8156,31 +8666,15 @@ $('#filePreview').addEventListener('click', async () => {
8156
8666
  toast((r && r.error) || 'preview açılamadı');
8157
8667
  return;
8158
8668
  }
8159
- /* ANDROID EMÜLATÖR: kuruluysa Expo uygulamaları emülatörde açılır —
8160
- AVD arka planda başlatılır (kendi penceresi açılır), ajan expo'yu kaldırır */
8161
- let ask = '';
8162
- if (kind === 'expo' || kind === 'react-native') {
8163
- const dev = await beast.androidStatus().catch(() => null);
8164
- const hasEmu = dev && dev.ok && dev.sdk && Array.isArray(dev.avds) && dev.avds.length;
8165
- if (hasEmu) {
8166
- beast.androidStartAvd(dev.avds[0]).catch(() => {});
8167
- toast('Android emülatörü başlatılıyor — ilk açılış 30-90 sn');
8168
- ask =
8169
- kind === 'expo'
8170
- ? 'Preview aç — bu klasör bir Expo/React Native projesi (app.json var). Android emülatörü başlatıldı; arka planda `npx expo start --android` çalıştır — uygulama emülatörde açılır (expo adb ile cihazı kendisi bulur). Çalışan http://localhost:PORT adresini raporda yaz.'
8171
- : 'Preview aç — bu klasör bir React Native projesi. Android emülatörü hazır. Metro\u2019yu arka planda başlat (scripts.start varsa `npm start`) ve uygulamayı emülatöre kur: `npx react-native run-android`. Sunucu adresini raporda yaz.';
8172
- }
8173
- }
8174
- if (!ask) {
8175
- ask =
8176
- kind === 'expo'
8177
- ? 'Preview aç — bu klasör bir Expo/React Native projesi (app.json var). Arka planda `npx expo start --tunnel` çalıştır (tunnel: telefon her ağdan bağlanabilir; web önizleme için `--web` de ekle). Sunucu kalkınca çalışan http://localhost:PORT adresini raporda yaz.'
8178
- : kind === 'react-native'
8179
- ? 'Preview aç — bu klasör bir React Native projesi. Metro sunucusunu arka planda başlat: package.json scripts.start varsa `npm start`, yoksa `npx react-native start`. Sunucu kalkınca çalışan http://localhost:PORT adresini raporda yaz.'
8180
- : kind === 'web'
8181
- ? 'Preview aç — bu proje dev-server ister. Doğru komutu package.json scripts\'ından seç (dev/start/serve — `npm run dev` vb.) ve arka planda başlat. Sunucu kalkınca çalışan http://localhost:PORT adresini raporda yaz.'
8182
- : 'Preview aç — bu projenin nasıl çalıştığını package.json / konfigürasyon dosyalarından anla, doğru dev komutunu arka planda başlat ve çalışan http://localhost:PORT adresini raporda yaz.';
8183
- }
8669
+ /* MOBİL: tunnel (telefon QR) yolu — emülatör entegrasyonu kaldırıldı */
8670
+ const ask =
8671
+ kind === 'expo'
8672
+ ? 'Preview aç — bu klasör bir Expo/React Native projesi (app.json var). Arka planda `npx expo start --tunnel` çalıştır (tunnel: telefon her ağdan bağlanabilir; web önizleme için `--web` de ekle). Sunucu kalkınca çalışan http://localhost:PORT adresini raporda yaz.'
8673
+ : kind === 'react-native'
8674
+ ? 'Preview aç — bu klasör bir React Native projesi. Metro sunucusunu arka planda başlat: package.json scripts.start varsa `npm start`, yoksa `npx react-native start`. Sunucu kalkınca çalışan http://localhost:PORT adresini raporda yaz.'
8675
+ : kind === 'web'
8676
+ ? 'Preview aç — bu proje dev-server ister. Doğru komutu package.json scripts\'ından seç (dev/start/serve — `npm run dev` vb.) ve arka planda başlat. Sunucu kalkınca çalışan http://localhost:PORT adresini raporda yaz.'
8677
+ : 'Preview aç — bu projenin nasıl çalıştığını package.json / konfigürasyon dosyalarından anla, doğru dev komutunu arka planda başlat ve çalışan http://localhost:PORT adresini raporda yaz.';
8184
8678
  if (els.bcInput) {
8185
8679
  els.bcInput.value = ask;
8186
8680
  bcInputResize();
@@ -8934,7 +9428,7 @@ function bcIngest(ev) {
8934
9428
  bcSetBusy(true); /* kuyruktan tetiklenen işte panel kendini kilitler */
8935
9429
  bcToolBoxStart(ev.callId, ev.name, ev.args);
8936
9430
  /* ajan dosya yazarsa/editlerse açık IDE sekmesini izlemeye al */
8937
- if ((ev.name === 'write_file' || ev.name === 'edit_file') && ev.args && ev.args.path) codeWriteWatch.set(ev.callId, ev.args.path);
9431
+ if ((ev.name === 'write_file' || ev.name === 'edit_file' || ev.name === 'write' || ev.name === 'edit') && ev.args && ev.args.path) codeWriteWatch.set(ev.callId, ev.args.path);
8938
9432
  break;
8939
9433
  case 'tool-end':
8940
9434
  bcToolBoxEnd(ev.callId, ev.ok, ev.result, ev.diff);
@@ -8944,7 +9438,7 @@ function bcIngest(ev) {
8944
9438
  codeReloadIfOpen(wp);
8945
9439
  }
8946
9440
  /* dosya/klasör üreten araçlardan sonra soldaki ağaç otomatik tazelensin */
8947
- if (ev.name === 'write_file' || ev.name === 'edit_file' || ev.name === 'run_command' || ev.name === 'python_run') {
9441
+ if (ev.name === 'write_file' || ev.name === 'edit_file' || ev.name === 'write' || ev.name === 'edit' || ev.name === 'run_command' || ev.name === 'python_run') {
8948
9442
  ideRefreshTree();
8949
9443
  }
8950
9444
  break;
@@ -8957,13 +9451,17 @@ function bcIngest(ev) {
8957
9451
  bcQrShow(ev.qr, ev.url);
8958
9452
  break;
8959
9453
  case 'bc-mode':
8960
- /* OpenCode disiplini çalışma modu: başlıkta rozet + panelde bilgi satırı */
8961
- if (els.bcTitle) {
8962
- els.bcTitle.textContent =
8963
- ev.mode === 'plan' ? 'BEAST CODE · PLAN' :
8964
- ev.mode === 'build' ? 'BEAST CODE · BUILD' : 'BEAST CODE';
9454
+ case 'bc-agent':
9455
+ /* opencode ajan değişimi: başlıkta rozet + panelde bilgi satırı */
9456
+ {
9457
+ const agentName = ev.agent || ev.mode || '';
9458
+ if (els.bcTitle) {
9459
+ els.bcTitle.textContent =
9460
+ agentName === 'plan' ? 'BEAST CODE · PLAN' :
9461
+ agentName === 'build' ? 'BEAST CODE · BUILD' : 'BEAST CODE';
9462
+ }
9463
+ bcLine('t-dim', '[' + (ev.body || agentName || 'ajan değişti') + ']');
8965
9464
  }
8966
- bcLine('t-dim', '[' + (ev.body || ev.mode || 'mod değişti') + ']');
8967
9465
  break;
8968
9466
  case 'done':
8969
9467
  bcFlushStream();
@@ -9409,7 +9907,7 @@ function stIngest(ev) {
9409
9907
  break;
9410
9908
  case 'tool-end':
9411
9909
  stToolBoxEnd(ev.callId, ev.ok, ev.result, ev.diff);
9412
- if (ev.name === 'write_file' || ev.name === 'edit_file' || ev.name === 'run_command' || ev.name === 'python_run') {
9910
+ if (ev.name === 'write_file' || ev.name === 'edit_file' || ev.name === 'write' || ev.name === 'edit' || ev.name === 'run_command' || ev.name === 'python_run') {
9413
9911
  ideRefreshTree(); /* ffmpeg çıktısı anında ağaca düşer */
9414
9912
  }
9415
9913
  break;
@@ -9640,7 +10138,7 @@ function sbIngest(ev) {
9640
10138
  const ok = ev.ok !== false;
9641
10139
  sbLine(ok ? 't-dim' : 't-err', (ok ? ' \u2713 ' : ' \u2717 ') + String(ev.result || '').replace(/\s+/g, ' ').slice(0, 160));
9642
10140
  /* ajan dosya yazarsa/editlerse soldaki repo ağacı canlı tazelenir */
9643
- if (ev.name === 'write_file' || ev.name === 'edit_file' || ev.name === 'run_command') ideRefreshTree();
10141
+ if (ev.name === 'write_file' || ev.name === 'edit_file' || ev.name === 'write' || ev.name === 'edit' || ev.name === 'run_command') ideRefreshTree();
9644
10142
  break;
9645
10143
  }
9646
10144
  case 'status':
@@ -9810,6 +10308,7 @@ async function sbClone(input) {
9810
10308
  function setSandboxMode(on) {
9811
10309
  if (on && ideModeOn()) { setIdeMode(false); }
9812
10310
  if (on && studioModeOn()) { setStudioMode(false); }
10311
+ if (on && financeModeOn()) { setFinanceMode(false); }
9813
10312
  document.body.classList.toggle('sandbox-mode', !!on);
9814
10313
  if (els.sbBtn) els.sbBtn.classList.toggle('on', !!on);
9815
10314
  /* marka + sol konsol başlığı: moduna göre BEAST Agent/Code/Studio/Sandbox */