beast-agent 2.56.0 → 2.57.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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "beast-agent",
3
3
  "productName": "Beast Agent",
4
- "version": "2.56.0",
4
+ "version": "2.57.0",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
@@ -7109,6 +7109,11 @@ Engine.prototype._pushAgentDm = function (dm) {
7109
7109
  this._agentDms = this._agentDms || [];
7110
7110
  this._agentDms.push(dm);
7111
7111
  if (this._agentDms.length > 600) this._agentDms.splice(0, this._agentDms.length - 600);
7112
+ /* DIŞ KÖPRÜ KANCASI (main.js: Telegram): panel kaydı düşen her DM tek
7113
+ noktadan dışarı bildirilir — köprü hangi mesajı göndereceğine kendi karar verir. */
7114
+ if (typeof this.onAgentDm === 'function') {
7115
+ try { this.onAgentDm(dm); } catch {}
7116
+ }
7112
7117
  };
7113
7118
 
7114
7119
  /* ---------- EKİP GRUBU: aynı görevde koşan paralel ajanlar otomatik
@@ -7214,6 +7219,66 @@ Engine.prototype._agentTeamPost = function (gid, fromSid, fromTitle, text) {
7214
7219
  } catch {}
7215
7220
  };
7216
7221
 
7222
+ /* Grubu hazırla (yoksa kur): Telegram köprüsü ve finans alarmları grubu önceden
7223
+ hiç kurulmamış olsa da panele düşebilsin. */
7224
+ Engine.prototype._agentGroupEnsure = function (gid, title, opts) {
7225
+ gid = String(gid || '');
7226
+ if (!gid) return null;
7227
+ let g = this._agentGroups.get(gid);
7228
+ if (!g) {
7229
+ g = {
7230
+ id: gid,
7231
+ title: String(title || gid),
7232
+ members: [],
7233
+ titles: {},
7234
+ createdAt: nowIso(),
7235
+ closed: false,
7236
+ team: !(opts && opts.system),
7237
+ };
7238
+ this._agentGroups.set(gid, g);
7239
+ }
7240
+ if (title && !g.title) g.title = String(title);
7241
+ g.closed = false;
7242
+ delete g.closedAt;
7243
+ return g;
7244
+ };
7245
+
7246
+ /* DIŞ KAYNAKLI (kullanıcı/Telegram) GRUP POSTU: panelde gerçek mesaj olarak
7247
+ görünür + istenirse koşan üyelere DM gibi teslim edilir. Sistem postlarından
7248
+ farkı: model tarafına da düşebilir (wake kararı çağıranın). */
7249
+ Engine.prototype.agentDmGroupPost = function (input) {
7250
+ try {
7251
+ const i = input || {};
7252
+ const gid = String(i.gid || '');
7253
+ const g = this._agentGroupEnsure(gid, i.title || '', { system: !!i.system });
7254
+ if (!g) return { ok: false, error: 'gid gerekli' };
7255
+ const dm = {
7256
+ at: nowIso(),
7257
+ from: String(i.fromSid || 'user'),
7258
+ fromTitle: String(i.fromTitle || 'Kullanıcı'),
7259
+ to: '',
7260
+ toTitle: 'ekip',
7261
+ group: gid,
7262
+ groupTitle: g.title,
7263
+ topic: String(i.topic || 'grup').slice(0, 60),
7264
+ text: String(i.text || '').slice(0, 4000),
7265
+ ...(i.system ? { system: true } : {}),
7266
+ ...(i.viaTelegram ? { viaTelegram: true } : {}),
7267
+ };
7268
+ this._pushAgentDm(dm);
7269
+ /* canlı event TEK SEFER: ilk koşan üye (yoksa gönderen) — panelde çoğalmaz */
7270
+ const firstRunning = (g.members || []).find((m) => {
7271
+ const j = this._bgJobs && this._bgJobs.get(m);
7272
+ return j && j.status === 'running';
7273
+ });
7274
+ emitSafe(this, firstRunning || String(i.fromSid || ''), { type: 'agent-dm', ...dm });
7275
+ this._persistAgentDms();
7276
+ return { ok: true, group: g.title };
7277
+ } catch (e) {
7278
+ return { ok: false, error: String((e && e.message) || e) };
7279
+ }
7280
+ };
7281
+
7217
7282
  /* Sistem promptuna ekip zorunluluğu */
7218
7283
  Engine.prototype._agentTeamPromptLine = function (gid) {
7219
7284
  const g = this._agentGroups && this._agentGroups.get(String(gid));
@@ -106,6 +106,8 @@ class TelegramBridge {
106
106
  username: String(msg.from.username || ''),
107
107
  senderName: String(msg.from.first_name || msg.from.username || ''),
108
108
  isGroup: !!(msg.chat && (msg.chat.type === 'group' || msg.chat.type === 'supergroup')),
109
+ chatTitle: String((msg.chat && (msg.chat.title || msg.chat.username)) || ''),
110
+ chatType: String((msg.chat && msg.chat.type) || 'private'),
109
111
  };
110
112
  try {
111
113
  if (this.onIncoming) this.onIncoming(String(chatId), payload);
package/src/main.js CHANGED
@@ -332,6 +332,18 @@ let tgCronSid = null; // Telegram CRON TABAN OTURUMU: otomatik işler; hiçbir k
332
332
  let tgHistory = new Map(); // chatId -> [sid,...]
333
333
  const TG_HISTORY_CAP = 20;
334
334
  let tgLastActiveChatId = ''; // en son mesaj gelen TG sohbeti — cron yansıtma hedefi
335
+ /* Botun gördüğü Telegram GRUPLARI: chatId -> {title, at}. Ajan DM ↔ Telegram
336
+ köprüsü kurulumunda grup seçimi bu listeden yapılır (BotFather'da gizlilik
337
+ kapatılmalı ya da bot gruba admin olmalı ki mesajlar düşsün). */
338
+ let tgGroups = {};
339
+ try {
340
+ const raw = settings.tgGroups;
341
+ if (raw && typeof raw === 'object') {
342
+ for (const [id, v] of Object.entries(raw)) {
343
+ if (v && typeof v === 'object') tgGroups[String(id)] = { title: String(v.title || ''), at: Number(v.at) || 0 };
344
+ }
345
+ }
346
+ } catch {}
335
347
  let dc = null;
336
348
  let dcChats = new Map(); // discord channelId -> KENDİ oturumu: her kanal AYRI (gizlilik)
337
349
  let dcCronSid = null; // Discord CRON TABAN OTURUMU: otomatik işler; hiçbir kişiye bağlı DEĞİL
@@ -672,6 +684,29 @@ function tgLog(line) {
672
684
  try { log.info('telegram', line); } catch {}
673
685
  }
674
686
 
687
+ /* Gelen grup mesajından grubu hatırla — ajan DM köprüsü grup seçicisi bu
688
+ listeden beslenir. Başlık değişmedikçe diske yazılmaz (yalnız tazeleme). */
689
+ function tgRememberGroup(chatId, title) {
690
+ const id = String(chatId || '');
691
+ if (!id) return;
692
+ const cur = tgGroups[id];
693
+ const t = String(title || '');
694
+ const at = Date.now();
695
+ if (cur && (cur.title === t || !t)) {
696
+ cur.at = at;
697
+ return;
698
+ }
699
+ tgGroups[id] = { title: t || (cur && cur.title) || id, at };
700
+ settings.tgGroups = tgGroups;
701
+ try { saveSettings(); } catch {}
702
+ }
703
+
704
+ function tgGroupsList() {
705
+ return Object.entries(tgGroups)
706
+ .map(([id, v]) => ({ id, title: String((v && v.title) || id), at: Number((v && v.at) || 0) }))
707
+ .sort((a, b) => b.at - a.at);
708
+ }
709
+
675
710
  function tgFind(senderId, username) {
676
711
  const list = settings.tgAllow || [];
677
712
  if (!list.length) return null; // boş liste = kimseye cevap yok
@@ -2895,8 +2930,20 @@ async function tgFlush(chatId) {
2895
2930
  async function handleTgIncoming(chatId, payload) {
2896
2931
  try {
2897
2932
  if (!engine) return;
2898
- /* v1: yalnız birebir sohbetler — grup davranışı WA'daki gibi ayrı toggle ile gelir */
2933
+ /* GRUP: normal grup desteği kapalı; ancak AJAN DM ↔ TELEGRAM köprüsünde
2934
+ seçili grupsa mesaj AJAN DM grubuna düşer ve finance ajanları uyanır. */
2899
2935
  if (payload.isGroup) {
2936
+ tgRememberGroup(chatId, payload.chatTitle);
2937
+ const dmTg = finDmTgCfg();
2938
+ if (dmTg.on && String(dmTg.chat) === String(chatId)) {
2939
+ const allowed = tgFind(payload.senderId, payload.username);
2940
+ tgLog(
2941
+ `grup(ajan dm) chat=${chatId} sender=${payload.senderId || '?'} user=${payload.username || '-'} allowed=${!!allowed}`
2942
+ );
2943
+ if (!allowed) return; /* izin listesi dışı yoksay */
2944
+ finTelegramToAgentDm(payload);
2945
+ return;
2946
+ }
2900
2947
  tgLog(`skip: grup mesajı chat=${chatId} (grup desteği kapalı)`);
2901
2948
  return;
2902
2949
  }
@@ -3566,6 +3613,10 @@ function reloadBackend() {
3566
3613
  düşünce sıradaki planlı turu BEKLEMEZ — ajan boştaysa tur hemen başlar
3567
3614
  (ajan-ajan DM'lerinde engine kancayı çağırmaz; ping-pong korunur). */
3568
3615
  engine.onDmQueued = (job) => { try { finWakeAgent(job && job.id); } catch {} };
3616
+ /* AJAN DM → TELEGRAM: AJAN DM grubuna düşen her kayıt (alarm/sistem postu
3617
+ dahil) tek kancadan geçer; köprü ayarı açıksa seçili Telegram grubuna
3618
+ yansıtılır (Telegram'dan gelen mesajlar viaTelegram ile geri gönderilmez). */
3619
+ engine.onAgentDm = (dm) => { try { finAgentDmToTelegram(dm); } catch {} };
3569
3620
  /* PANEL RUN köprüsü: ajanın panel_run aracı → ÇALIŞTIR panelindeki yönetilen
3570
3621
  süreç koşucusu (sandbox:run IPC ile aynı makine). */
3571
3622
  engine.sbRunHook = sbRunStartManaged;
@@ -7683,14 +7734,15 @@ function watcherFire(w, value) {
7683
7734
  ? 'izlenen değer değişti'
7684
7735
  : `kural sağlandı (son değer ${value}, koşul ${w.op} ${w.value ?? ''})`;
7685
7736
  toastNotify(`İzleyici: ${w.name}`, target, 'watchers');
7686
- /* FINANCE AJANI: izleyici alarmı AJAN DM'i olarak düşer ve boştaki ajanı
7687
- tur beklemeden uyandırır (main.js finWakeAgentDm). Diğer oturumlarda
7688
- eski davranış: mesaj doğrudan sohbete iner. */
7689
- if (financeState.agents.has(String(sid))) {
7690
- finWakeAgentDm(
7691
- sid,
7737
+ /* FINANCE AJANI: izleyici alarmı AJAN DM GRUBUNA bildirim olarak düşer ve
7738
+ koşan TÜM finance ajanlarını tur beklemeden uyandırır (finWakeAgents:
7739
+ grup postu + dm+wake). Diğer oturumlarda eski davranış: mesaj doğrudan
7740
+ sohbete iner. */
7741
+ if (financeState.agents.has(String(sid)) || (financeState.mode && financeState.agents.size)) {
7742
+ finWakeAgents(
7692
7743
  `[FİNANS OLAYI — İZLEYİCİ ALARMI: ${w.name}]\n- ${target}\n` +
7693
- `Şimdi yap: izleyiciyi kurma nedenini hatırla; durumu değerlendir, gerekiyorsa işlem/uyarı üret. Kısa rapor ver.`
7744
+ `Şimdi yap: izleyiciyi kurma nedenini hatırla; durumu değerlendir, gerekiyorsa işlem/uyarı üret. Kısa rapor ver.`,
7745
+ { kind: 'watcher', watcher: w.id }
7694
7746
  );
7695
7747
  return;
7696
7748
  }
@@ -9349,6 +9401,12 @@ function finCfg() {
9349
9401
  iptal) düşer; fiyat alarmı, koruma (BE/trailing/kısmi TP), günlük limit ve
9350
9402
  rapor bildirimleri panelde kalır. false = tüm olaylar kanallara gider. */
9351
9403
  if (typeof f.notifyTradeOnly !== 'boolean') f.notifyTradeOnly = true;
9404
+ /* AJAN DM ↔ TELEGRAM KÖPRÜSÜ: açıkken `team:finance` AJAN DM grubundaki
9405
+ mesajlar seçilen Telegram grubuna düşer; o gruba yazılan mesaj AJAN DM
9406
+ grubunda görünür ve koşan finance ajanları tur beklemeden uyanır. */
9407
+ if (typeof f.dmTelegram !== 'boolean') f.dmTelegram = false;
9408
+ if (typeof f.dmTelegramChat !== 'string') f.dmTelegramChat = '';
9409
+ if (typeof f.dmTelegramTitle !== 'string') f.dmTelegramTitle = '';
9352
9410
  if (!Number.isFinite(Number(f.maxDailyLossPct))) f.maxDailyLossPct = 3;
9353
9411
  /* GÜNLÜK LİMİT AKSİYONU: warn = yalnız uyarı; stop = tüm finance ajanlarını
9354
9412
  durdur; flatten = durdur + tüm pozisyonları kapat */
@@ -10001,6 +10059,76 @@ async function finStatsRefresh(force) {
10001
10059
  }
10002
10060
  }
10003
10061
 
10062
+ /* ---- AJAN DM ↔ TELEGRAM KÖPRÜSÜ ----
10063
+ Beast Finance görünüm ayarlarından açılır: `team:finance` AJAN DM grubundaki
10064
+ her mesaj (alarm/sistem postları dahil) seçilen Telegram grubuna yansır;
10065
+ o gruba yazılan mesaj AJAN DM grubunda görünür ve koşan finance ajanları
10066
+ tur beklemeden uyandırılır. Ayarlar: finCfg().dmTelegram / dmTelegramChat. */
10067
+ const FIN_TEAM_GID = 'team:finance';
10068
+ const FIN_TEAM_TITLE = 'Beast Finance EKİP';
10069
+
10070
+ /* Köprü ayarı (tek nokta): açık + grup seçili mi? */
10071
+ function finDmTgCfg() {
10072
+ let f = {};
10073
+ try { f = finCfg(); } catch {}
10074
+ return {
10075
+ on: !!(f && f.dmTelegram && f.dmTelegramChat),
10076
+ chat: String((f && f.dmTelegramChat) || ''),
10077
+ title: String((f && f.dmTelegramTitle) || ''),
10078
+ };
10079
+ }
10080
+
10081
+ /* Giden: AJAN DM grubu → Telegram (Telegram'dan gelenler geri gönderilmez). */
10082
+ function finAgentDmToTelegram(dm) {
10083
+ try {
10084
+ if (!dm || dm.viaTelegram) return;
10085
+ if (String(dm.group || '') !== FIN_TEAM_GID) return;
10086
+ const c = finDmTgCfg();
10087
+ if (!c.on || !tg || !tg.connected) return;
10088
+ const who = String(dm.fromTitle || (dm.system ? 'SİSTEM' : 'Ajan'));
10089
+ const body =
10090
+ `👥 *${String(dm.groupTitle || FIN_TEAM_TITLE)}* · ${who}\n` +
10091
+ String(dm.text || '').slice(0, 3800);
10092
+ Promise.resolve(tg.send(c.chat, body)).catch((e) =>
10093
+ tgLog('ajan dm → telegram hata: ' + String((e && e.message) || e))
10094
+ );
10095
+ } catch {}
10096
+ }
10097
+
10098
+ /* Gelen: Telegram grubu → AJAN DM grubu (+ tüm koşan finance ajanlarını uyandır). */
10099
+ function finTelegramToAgentDm(payload) {
10100
+ try {
10101
+ const body = String((payload && payload.text) || '').trim();
10102
+ if (!body || !engine) return { ok: false };
10103
+ const who = String((payload && (payload.senderName || payload.username)) || 'Telegram');
10104
+ const label = `Sahip · Telegram (${who})`;
10105
+ try {
10106
+ if (typeof engine.agentDmGroupPost === 'function') {
10107
+ engine.agentDmGroupPost({
10108
+ gid: FIN_TEAM_GID,
10109
+ title: FIN_TEAM_TITLE,
10110
+ fromSid: 'tg:' + String((payload && payload.senderId) || ''),
10111
+ fromTitle: label,
10112
+ topic: 'telegram',
10113
+ text: body,
10114
+ viaTelegram: true,
10115
+ });
10116
+ }
10117
+ } catch {}
10118
+ const wake =
10119
+ `[AJAN DM — TELEGRAM GRUBU · ${label}]\n${body}\n` +
10120
+ 'Şimdi yap: sahibin Telegram grubundan gelen bu mesajı değerlendir; gerekiyorsa ekipçe aksiyon al. Kısa rapor ver.';
10121
+ let sent = 0;
10122
+ for (const [sid] of financeState.agents) {
10123
+ if (finWakeAgentDm(sid, wake)) sent += 1;
10124
+ }
10125
+ financeLog(`[telegram] ajan dm grubuna mesaj düştü (${sent} ajan uyandı)`);
10126
+ return { ok: true, sent };
10127
+ } catch {
10128
+ return { ok: false };
10129
+ }
10130
+ }
10131
+
10004
10132
  /* ---- FİNANS OLAY → AJAN DM UYANDIRMA ----
10005
10133
  Stop/TP/bekleyen emir aktivasyonu/fiyat alarmı gibi olaylar koşan finance
10006
10134
  ajanlarına AJAN DM'i olarak düşer ve BOŞTAKİ ajanı TUR SANİYESİNİ
@@ -10031,20 +10159,32 @@ function finWakeAgentDm(sid, text) {
10031
10159
  }
10032
10160
  }
10033
10161
 
10034
- /* Tüm koşan finance ajanlarını olayla uyandır. Dönüş: uyandırılan ajan sayısı. */
10162
+ /* Tüm koşan finance ajanlarını olayla uyandır. Dönüş: uyandırılan ajan sayısı.
10163
+ Olay HER durumda ekip DM grubuna sistem postu olarak düşer (koşan ajan yoksa
10164
+ bile panelde görünür — grup yoksa kurulur). */
10035
10165
  function finWakeAgents(text, meta) {
10036
10166
  const body = String(text || '').trim();
10037
10167
  if (!body || !engine) return 0;
10038
- if (!financeState.agents.size) {
10039
- financeLog('[olay] koşan finance ajanı yok — uyarı yalnız günlük/bildirimde' + (meta && meta.kind ? ' (' + meta.kind + ')' : ''));
10040
- return 0;
10041
- }
10042
10168
  /* Panel görünürlüğü: olay ekip DM grubuna sistem postu olarak düşer */
10043
10169
  try {
10044
- if (typeof engine._agentTeamPost === 'function') {
10170
+ if (typeof engine.agentDmGroupPost === 'function') {
10171
+ engine.agentDmGroupPost({
10172
+ gid: FIN_TEAM_GID,
10173
+ title: FIN_TEAM_TITLE,
10174
+ fromSid: 'finance',
10175
+ fromTitle: 'Beast Finance',
10176
+ topic: 'ortak görev',
10177
+ text: body,
10178
+ system: true,
10179
+ });
10180
+ } else if (typeof engine._agentTeamPost === 'function') {
10045
10181
  engine._agentTeamPost('team:finance', 'finance', 'Beast Finance', body);
10046
10182
  }
10047
10183
  } catch {}
10184
+ if (!financeState.agents.size) {
10185
+ financeLog('[olay] koşan finance ajanı yok — uyarı yalnız günlük/bildirimde' + (meta && meta.kind ? ' (' + meta.kind + ')' : ''));
10186
+ return 0;
10187
+ }
10048
10188
  let sent = 0;
10049
10189
  for (const [sid] of financeState.agents) {
10050
10190
  if (finWakeAgentDm(sid, body)) sent += 1;
@@ -11473,6 +11613,13 @@ ipcMain.handle('finance:settings', async (_e, patch) => {
11473
11613
  if (p.notifyTrades !== undefined) f.notifyTrades = !!p.notifyTrades;
11474
11614
  if (p.notifyWatchdog !== undefined) f.notifyWatchdog = !!p.notifyWatchdog;
11475
11615
  if (p.notifyTradeOnly !== undefined) f.notifyTradeOnly = !!p.notifyTradeOnly;
11616
+ /* AJAN DM ↔ TELEGRAM köprüsü ayarları */
11617
+ if (p.dmTelegram !== undefined) f.dmTelegram = !!p.dmTelegram;
11618
+ if (p.dmTelegramChat !== undefined) {
11619
+ f.dmTelegramChat = String(p.dmTelegramChat || '').trim().slice(0, 64);
11620
+ if (!f.dmTelegramChat) f.dmTelegram = false;
11621
+ }
11622
+ if (p.dmTelegramTitle !== undefined) f.dmTelegramTitle = String(p.dmTelegramTitle || '').trim().slice(0, 80);
11476
11623
  if (p.maxDailyLossPct !== undefined) f.maxDailyLossPct = Math.max(0, Math.min(50, Number(p.maxDailyLossPct) || 0));
11477
11624
  if (p.dailyLossAction !== undefined) {
11478
11625
  const v = String(p.dailyLossAction || 'warn').toLowerCase();
@@ -13877,6 +14024,8 @@ ipcMain.handle('tg:allow:set', (_e, list) => {
13877
14024
  saveSettings();
13878
14025
  return settings.tgAllow;
13879
14026
  });
14027
+ /* Botun gördüğü gruplar (AJAN DM ↔ TELEGRAM grup seçicisi) */
14028
+ ipcMain.handle('tg:groups:list', () => tgGroupsList());
13880
14029
  ipcMain.handle('tg:sessions', () => [...tgChats.values()]);
13881
14030
 
13882
14031
  /* Sohbet geçmişi etiketi (Telegram): "Telegram <ad|chatId>" */
package/src/preload.js CHANGED
@@ -221,6 +221,7 @@ contextBridge.exposeInMainWorld('beast', {
221
221
  tgStop: () => ipcRenderer.invoke('tg:stop'),
222
222
  tgGetAllow: () => ipcRenderer.invoke('tg:allow:get'),
223
223
  tgSetAllow: (list) => ipcRenderer.invoke('tg:allow:set', list),
224
+ tgGroupsList: () => ipcRenderer.invoke('tg:groups:list'),
224
225
  tgListSessions: () => ipcRenderer.invoke('tg:sessions'),
225
226
  onTgEvent: (cb) => ipcRenderer.on('tg:event', (_e, ev) => cb(ev)),
226
227
  dcGetStatus: () => ipcRenderer.invoke('dc:status:get'),
@@ -891,6 +891,17 @@
891
891
  <span class="fsr-main"><b>AKIŞ</b><i>Ajan ve MT5 olay günlüğü</i></span>
892
892
  </label>
893
893
  <div class="fin-set-note">RİSK OTOMASYONU gizliyken koruma döngüsü (watchdog) kapatılır; tekrar gösterdiğinde ilgili kutucuktan açabilirsin.</div>
894
+ <div class="fin-set-sep"></div>
895
+ <label class="fin-set-row" title="Açıkken Beast Finance AJAN DM grubundaki mesajlar (alarm/sistem postları dahil) seçtiğin Telegram grubuna düşer; o gruba yazdığın mesaj AJAN DM grubunda görünür ve koşan finance ajanları tur beklemeden uyanır.">
896
+ <input id="finDmTgOn" type="checkbox" />
897
+ <span class="fsr-main"><b>AJAN DM ↔ TELEGRAM</b><i>Ajan DM grubu Telegram'da canlı — iki yönlü köprü</i></span>
898
+ </label>
899
+ <div id="finDmTgWrap" class="fin-set-sub" hidden>
900
+ <span class="fin-set-lbl">TELEGRAM GRUBU</span>
901
+ <select id="finDmTgChat" title="Botu eklediğin Telegram grubunu seç — liste, bottan mesaj alınan grupları gösterir"></select>
902
+ <button id="finDmTgRefresh" class="fin-set-btn" title="Grup listesini yenile — bottan mesaj alan gruplar listelenir">&#x27F3; Listeyi yenile</button>
903
+ <div id="finDmTgHint" class="fin-set-note" style="margin:2px 0 0"></div>
904
+ </div>
894
905
  </div>
895
906
  </div>
896
907
  </div>
@@ -203,6 +203,11 @@ const els = {
203
203
  finShowNotify: $('#finShowNotify'),
204
204
  finShowPerf: $('#finShowPerf'),
205
205
  finShowFlow: $('#finShowFlow'),
206
+ finDmTgOn: $('#finDmTgOn'),
207
+ finDmTgWrap: $('#finDmTgWrap'),
208
+ finDmTgChat: $('#finDmTgChat'),
209
+ finDmTgRefresh: $('#finDmTgRefresh'),
210
+ finDmTgHint: $('#finDmTgHint'),
206
211
  finRiskCard: $('#finRiskCard'),
207
212
  finNotifyCard: $('#finNotifyCard'),
208
213
  finPerfCard: $('#finPerfCard'),
@@ -10119,6 +10124,11 @@ async function finSnapshot() {
10119
10124
  if (!finView.risk && r.cfg && r.cfg.watchdog !== false) finSaveCfg({ watchdog: false });
10120
10125
  finRenderStats(r.stats, r.equity, r.account && r.account.currency ? r.account.currency : '');
10121
10126
  finRenderAlerts(r.alerts);
10127
+ /* görünüm ayarları açıkken AJAN DM ↔ TELEGRAM seçimi cfg ile senkron kalsın
10128
+ (kullanıcı dropdown'dayken DOM'a dokunulmaz) */
10129
+ if (els.finSettingsOverlay && !els.finSettingsOverlay.hidden && document.activeElement !== els.finDmTgChat) {
10130
+ finDmTgPaint();
10131
+ }
10122
10132
  }
10123
10133
 
10124
10134
  function finLogLine(line) {
@@ -10280,8 +10290,63 @@ function finViewApply() {
10280
10290
  if (!finView.risk) finSaveCfg({ watchdog: false });
10281
10291
  }
10282
10292
 
10293
+ /* ---------- AJAN DM ↔ TELEGRAM (görünüm ayarları) ----------
10294
+ Ayarlar settings.finance'ta: dmTelegram + dmTelegramChat/Title. Grup listesi
10295
+ bottan mesaj alan gruplardan gelir (kanal: tg:groups:list). */
10296
+ let finDmTgGroups = [];
10297
+
10298
+ function finDmTgPaint() {
10299
+ if (!els.finDmTgOn) return;
10300
+ const cfg = finCfgCache || {};
10301
+ const on = cfg.dmTelegram === true;
10302
+ els.finDmTgOn.checked = on;
10303
+ const row = els.finDmTgOn.closest('.fin-set-row');
10304
+ if (row) row.classList.toggle('on', on);
10305
+ if (els.finDmTgWrap) els.finDmTgWrap.hidden = !on;
10306
+ if (els.finDmTgChat) {
10307
+ const cur = String(cfg.dmTelegramChat || '');
10308
+ const sel = els.finDmTgChat;
10309
+ sel.textContent = '';
10310
+ const opt0 = document.createElement('option');
10311
+ opt0.value = '';
10312
+ opt0.textContent = finDmTgGroups.length ? '— Grup seç —' : '— Görünen grup yok —';
10313
+ sel.appendChild(opt0);
10314
+ let has = false;
10315
+ for (const g of finDmTgGroups) {
10316
+ const o = document.createElement('option');
10317
+ o.value = String(g.id);
10318
+ o.textContent = String(g.title || g.id) + ' (' + g.id + ')';
10319
+ if (String(g.id) === cur) { o.selected = true; has = true; }
10320
+ sel.appendChild(o);
10321
+ }
10322
+ /* listelenmemiş kayıtlı grup kaybolmasın — mevcut seçim korunur */
10323
+ if (cur && !has) {
10324
+ const o = document.createElement('option');
10325
+ o.value = cur;
10326
+ o.textContent = String(cfg.dmTelegramTitle || cur) + ' (' + cur + ')';
10327
+ o.selected = true;
10328
+ sel.appendChild(o);
10329
+ }
10330
+ }
10331
+ if (els.finDmTgHint) {
10332
+ els.finDmTgHint.textContent = finDmTgGroups.length
10333
+ ? 'Seçili gruptaki mesajlar AJAN DM grubunda görünür; AJAN DM mesajları da bu gruba düşer.'
10334
+ : 'Botu bir Telegram grubuna ekle, gruba bir mesaj yaz ve "Listeyi yenile"ye bas (BotFather → /setprivacy → Disable ya da bot gruba admin olmalı).';
10335
+ }
10336
+ }
10337
+
10338
+ async function finDmTgRefresh() {
10339
+ try {
10340
+ const list = await beast.tgGroupsList();
10341
+ finDmTgGroups = Array.isArray(list) ? list : [];
10342
+ } catch { finDmTgGroups = []; }
10343
+ finDmTgPaint();
10344
+ }
10345
+
10283
10346
  function finSettingsOpen() {
10284
10347
  if (els.finSettingsOverlay) els.finSettingsOverlay.hidden = false;
10348
+ finDmTgPaint();
10349
+ finDmTgRefresh();
10285
10350
  }
10286
10351
  function finSettingsCloseModal() {
10287
10352
  if (els.finSettingsOverlay) els.finSettingsOverlay.hidden = true;
@@ -10315,6 +10380,30 @@ if (els.finShowRisk) els.finShowRisk.addEventListener('change', () => finViewTog
10315
10380
  if (els.finShowNotify) els.finShowNotify.addEventListener('change', () => finViewToggle('notify', els.finShowNotify.checked));
10316
10381
  if (els.finShowPerf) els.finShowPerf.addEventListener('change', () => finViewToggle('perf', els.finShowPerf.checked));
10317
10382
  if (els.finShowFlow) els.finShowFlow.addEventListener('change', () => finViewToggle('flow', els.finShowFlow.checked));
10383
+ /* AJAN DM ↔ TELEGRAM köprüsü kontrolleri */
10384
+ if (els.finDmTgOn) {
10385
+ els.finDmTgOn.addEventListener('change', () => {
10386
+ const on = els.finDmTgOn.checked;
10387
+ if (finCfgCache) finCfgCache.dmTelegram = on;
10388
+ finSaveCfg({ dmTelegram: on });
10389
+ finDmTgPaint();
10390
+ toast(on ? 'AJAN DM ↔ Telegram köprüsü açıldı' : 'AJAN DM ↔ Telegram köprüsü kapatıldı');
10391
+ if (on) finDmTgRefresh();
10392
+ });
10393
+ }
10394
+ if (els.finDmTgChat) {
10395
+ els.finDmTgChat.addEventListener('change', () => {
10396
+ const id = els.finDmTgChat.value || '';
10397
+ const g = finDmTgGroups.find((x) => String(x.id) === String(id));
10398
+ if (finCfgCache) {
10399
+ finCfgCache.dmTelegramChat = id;
10400
+ finCfgCache.dmTelegramTitle = g ? String(g.title || '') : '';
10401
+ }
10402
+ finSaveCfg({ dmTelegramChat: id, dmTelegramTitle: g ? String(g.title || '') : '' });
10403
+ toast(id ? 'Telegram grubu bağlandı: ' + (g ? g.title || id : id) : 'Telegram grubu seçilmedi');
10404
+ });
10405
+ }
10406
+ if (els.finDmTgRefresh) els.finDmTgRefresh.addEventListener('click', () => finDmTgRefresh());
10318
10407
  finViewApply();
10319
10408
 
10320
10409
  /* ---------- BÖLÜM SIRALAMA (sürükle-bırak) ----------
@@ -5869,3 +5869,23 @@ body.finance-mode #composerWrap { margin-right: var(--finW, 400px); }
5869
5869
  .fin-set-row .fsr-main b { font-size: 12px; color: var(--text); letter-spacing: 0.4px; }
5870
5870
  .fin-set-row .fsr-main i { font-style: normal; font-size: 10.5px; color: var(--muted); }
5871
5871
  .fin-set-note { margin-top: 8px; font-size: 10.5px; color: var(--muted); line-height: 1.4; }
5872
+ .fin-set-sep { height: 1px; margin: 10px 0; background: var(--border); }
5873
+ .fin-set-sub {
5874
+ display: flex;
5875
+ flex-direction: column;
5876
+ gap: 6px;
5877
+ margin: 2px 0 6px;
5878
+ padding: 9px 10px;
5879
+ background: var(--panel);
5880
+ border: 1px solid var(--border);
5881
+ border-radius: 9px;
5882
+ }
5883
+ .fin-set-sub[hidden] { display: none; }
5884
+ .fin-set-lbl { font-size: 10.5px; color: var(--muted); letter-spacing: 0.5px; }
5885
+ .fin-set-sub select { width: 100%; }
5886
+ .fin-set-btn {
5887
+ align-self: flex-start;
5888
+ font-size: 10.5px;
5889
+ padding: 3px 8px;
5890
+ cursor: pointer;
5891
+ }