beast-agent 2.56.0 → 2.57.1

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.1",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
@@ -6178,7 +6178,7 @@ const TOOLS = [
6178
6178
  everyMin: { type: 'number', description: 'Check interval in minutes (default 15, min 1, max 1440)' },
6179
6179
  everySec: { type: 'number', description: 'Check interval in SECONDS (10-8640) — overrides everyMin for fast watchers (e.g. 30 = every 30s)' },
6180
6180
  cooldownMin: { type: 'number', description: 'İki bildirim arası en az dakika (varsayılan 60). >0 ise alarm TEKRARLIDIR: koşul sürdükçe soğuma dolduğunda yeniden bildirir (ör. 5 = 5 dakikada bir hatırlat). 0 = yalnız koşul yeniden sağlandığında (kenar tetikleme).' },
6181
- once: { type: 'boolean', description: 'true = tek seferlik izleyici: ilk tetiklemede kapanır (varsayılan false = tekrarlı)' },
6181
+ once: { type: 'boolean', description: 'true = tek seferlik izleyici: ilk tetiklemede listeden OTOMATİK SİLİNİR (varsayılan false = tekrarlı)' },
6182
6182
  },
6183
6183
  required: ['name', 'kind'],
6184
6184
  },
@@ -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);
@@ -194,7 +194,7 @@ function normalize(input) {
194
194
  everyMin: Math.max(1, Math.round(everySec / 60)), // geriye dönük uyum
195
195
  everySec,
196
196
  cooldownMin,
197
- once: !!i.once, /* tek seferlik: ilk tetiklemede kapanır */
197
+ once: !!i.once, /* tek seferlik: ilk tetiklemede listeden OTOMATİK SİLİNİR */
198
198
  prompt,
199
199
  sessionId: i.sessionId || null,
200
200
  enabled: true,
@@ -378,10 +378,11 @@ async function tickOnce(deps = {}) {
378
378
  w.lastError = '';
379
379
  pushLog(w.id, r.triggered ? 'trigger' : 'ok', value);
380
380
  if (r.triggered) {
381
- /* TEK SEFERLİK: ilk tetiklemede izleyici kapanır (yeniden etkinleştirilebilir) */
382
- if (w.once) w.enabled = false;
383
381
  if (typeof hooks.onTrigger === 'function') hooks.onTrigger({ ...w }, value);
384
382
  events.push({ id: w.id, name: w.name, value });
383
+ /* TEK SEFERLİK: ilk tetiklemede izleyici listeden OTOMATİK SİLİNİR —
384
+ pasif olarak listede kalmaz (logları da temizlenir). */
385
+ if (w.once) remove(w.id);
385
386
  }
386
387
  } catch (e) {
387
388
  w.lastCheckAt = new Date(now).toISOString();
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,9 +2930,36 @@ 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
+ köprü AÇIKKEN gruba yazılan ilk mesaj o grubu OTOMATİK bağlar —
2935
+ grup seçme adımı yok. Bağlı gruptan gelen mesaj AJAN DM grubuna düşer
2936
+ ve finance ajanları uyanır. */
2899
2937
  if (payload.isGroup) {
2900
- tgLog(`skip: grup mesajı chat=${chatId} (grup desteği kapalı)`);
2938
+ tgRememberGroup(chatId, payload.chatTitle);
2939
+ const dmTg = finDmTgCfg();
2940
+ if (!dmTg.on) {
2941
+ tgLog(`skip: grup mesajı chat=${chatId} (köprü kapalı)`);
2942
+ return;
2943
+ }
2944
+ const allowed = tgFind(payload.senderId, payload.username);
2945
+ if (!allowed) {
2946
+ tgLog(`skip: grup mesajı chat=${chatId} — gönderici izin listesinde değil`);
2947
+ return;
2948
+ }
2949
+ if (!dmTg.chat) {
2950
+ /* İLK MESAJ = BAĞLAMA: /start gibi kısa komutlar yalnız bağlar,
2951
+ normal mesaj hem bağlar hem AJAN DM'e düşer. */
2952
+ finDmTgBind(chatId, payload.chatTitle);
2953
+ const boot = /^\/(start|baglan|bağlan)\b/i.test(String(payload.text || '').trim());
2954
+ if (boot) return;
2955
+ finTelegramToAgentDm(payload);
2956
+ return;
2957
+ }
2958
+ if (String(dmTg.chat) === String(chatId)) {
2959
+ finTelegramToAgentDm(payload);
2960
+ return;
2961
+ }
2962
+ tgLog(`skip: grup mesajı chat=${chatId} (bağlı grup ${dmTg.chat})`);
2901
2963
  return;
2902
2964
  }
2903
2965
  const hit = tgFind(payload.senderId, payload.username);
@@ -3566,6 +3628,10 @@ function reloadBackend() {
3566
3628
  düşünce sıradaki planlı turu BEKLEMEZ — ajan boştaysa tur hemen başlar
3567
3629
  (ajan-ajan DM'lerinde engine kancayı çağırmaz; ping-pong korunur). */
3568
3630
  engine.onDmQueued = (job) => { try { finWakeAgent(job && job.id); } catch {} };
3631
+ /* AJAN DM → TELEGRAM: AJAN DM grubuna düşen her kayıt (alarm/sistem postu
3632
+ dahil) tek kancadan geçer; köprü ayarı açıksa seçili Telegram grubuna
3633
+ yansıtılır (Telegram'dan gelen mesajlar viaTelegram ile geri gönderilmez). */
3634
+ engine.onAgentDm = (dm) => { try { finAgentDmToTelegram(dm); } catch {} };
3569
3635
  /* PANEL RUN köprüsü: ajanın panel_run aracı → ÇALIŞTIR panelindeki yönetilen
3570
3636
  süreç koşucusu (sandbox:run IPC ile aynı makine). */
3571
3637
  engine.sbRunHook = sbRunStartManaged;
@@ -7683,14 +7749,15 @@ function watcherFire(w, value) {
7683
7749
  ? 'izlenen değer değişti'
7684
7750
  : `kural sağlandı (son değer ${value}, koşul ${w.op} ${w.value ?? ''})`;
7685
7751
  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,
7752
+ /* FINANCE AJANI: izleyici alarmı AJAN DM GRUBUNA bildirim olarak düşer ve
7753
+ koşan TÜM finance ajanlarını tur beklemeden uyandırır (finWakeAgents:
7754
+ grup postu + dm+wake). Diğer oturumlarda eski davranış: mesaj doğrudan
7755
+ sohbete iner. */
7756
+ if (financeState.agents.has(String(sid)) || (financeState.mode && financeState.agents.size)) {
7757
+ finWakeAgents(
7692
7758
  `[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.`
7759
+ `Şimdi yap: izleyiciyi kurma nedenini hatırla; durumu değerlendir, gerekiyorsa işlem/uyarı üret. Kısa rapor ver.`,
7760
+ { kind: 'watcher', watcher: w.id }
7694
7761
  );
7695
7762
  return;
7696
7763
  }
@@ -9349,6 +9416,12 @@ function finCfg() {
9349
9416
  iptal) düşer; fiyat alarmı, koruma (BE/trailing/kısmi TP), günlük limit ve
9350
9417
  rapor bildirimleri panelde kalır. false = tüm olaylar kanallara gider. */
9351
9418
  if (typeof f.notifyTradeOnly !== 'boolean') f.notifyTradeOnly = true;
9419
+ /* AJAN DM ↔ TELEGRAM KÖPRÜSÜ: açıkken `team:finance` AJAN DM grubundaki
9420
+ mesajlar seçilen Telegram grubuna düşer; o gruba yazılan mesaj AJAN DM
9421
+ grubunda görünür ve koşan finance ajanları tur beklemeden uyanır. */
9422
+ if (typeof f.dmTelegram !== 'boolean') f.dmTelegram = false;
9423
+ if (typeof f.dmTelegramChat !== 'string') f.dmTelegramChat = '';
9424
+ if (typeof f.dmTelegramTitle !== 'string') f.dmTelegramTitle = '';
9352
9425
  if (!Number.isFinite(Number(f.maxDailyLossPct))) f.maxDailyLossPct = 3;
9353
9426
  /* GÜNLÜK LİMİT AKSİYONU: warn = yalnız uyarı; stop = tüm finance ajanlarını
9354
9427
  durdur; flatten = durdur + tüm pozisyonları kapat */
@@ -10001,6 +10074,103 @@ async function finStatsRefresh(force) {
10001
10074
  }
10002
10075
  }
10003
10076
 
10077
+ /* ---- AJAN DM ↔ TELEGRAM KÖPRÜSÜ ----
10078
+ Beast Finance görünüm ayarlarından açılır: `team:finance` AJAN DM grubundaki
10079
+ her mesaj (alarm/sistem postları dahil) seçilen Telegram grubuna yansır;
10080
+ o gruba yazılan mesaj AJAN DM grubunda görünür ve koşan finance ajanları
10081
+ tur beklemeden uyandırılır. Ayarlar: finCfg().dmTelegram / dmTelegramChat. */
10082
+ const FIN_TEAM_GID = 'team:finance';
10083
+ const FIN_TEAM_TITLE = 'Beast Finance EKİP';
10084
+
10085
+ /* Köprü ayarı: açık mı + bağlı grup (chatId). Grup SEÇİLMEZ — köprü açıkken
10086
+ gruba yazılan ilk mesaj o grubu otomatik bağlar (finDmTgBind). */
10087
+ function finDmTgCfg() {
10088
+ let f = {};
10089
+ try { f = finCfg(); } catch {}
10090
+ return {
10091
+ on: !!(f && f.dmTelegram),
10092
+ chat: String((f && f.dmTelegramChat) || ''),
10093
+ title: String((f && f.dmTelegramTitle) || ''),
10094
+ };
10095
+ }
10096
+
10097
+ /* Otomatik bağlama: gelen grup mesajı (izinli gönderici) AJAN DM aynası olur. */
10098
+ function finDmTgBind(chatId, title) {
10099
+ const id = String(chatId || '');
10100
+ if (!id) return false;
10101
+ const f = finCfg();
10102
+ const t = String(title || '').trim();
10103
+ f.dmTelegramChat = id;
10104
+ f.dmTelegramTitle = t || f.dmTelegramTitle || id;
10105
+ try { saveSettings(); } catch {}
10106
+ tgLog(`ajan dm telegram grubu OTOMATİK bağlandı: ${id}${t ? ' (' + t + ')' : ''}`);
10107
+ /* gruba onay: kullanıcı bağlandığını net görsün */
10108
+ try {
10109
+ if (tg && tg.connected) {
10110
+ Promise.resolve(
10111
+ tg.send(
10112
+ id,
10113
+ '✅ Beast Finance AJAN DM bu gruba bağlandı.\n' +
10114
+ 'Bundan sonra ajanların/alarmların mesajları burada; buraya yazdıkların AJAN DM grubunda görünür ve ajanları uyandırır.'
10115
+ )
10116
+ ).catch(() => {});
10117
+ }
10118
+ } catch {}
10119
+ return true;
10120
+ }
10121
+
10122
+ /* Giden: AJAN DM grubu → Telegram (Telegram'dan gelenler geri gönderilmez;
10123
+ grup henüz otomatik bağlanmadıysa sessizce beklenir). */
10124
+ function finAgentDmToTelegram(dm) {
10125
+ try {
10126
+ if (!dm || dm.viaTelegram) return;
10127
+ if (String(dm.group || '') !== FIN_TEAM_GID) return;
10128
+ const c = finDmTgCfg();
10129
+ if (!c.on || !c.chat || !tg || !tg.connected) return;
10130
+ const who = String(dm.fromTitle || (dm.system ? 'SİSTEM' : 'Ajan'));
10131
+ const body =
10132
+ `👥 *${String(dm.groupTitle || FIN_TEAM_TITLE)}* · ${who}\n` +
10133
+ String(dm.text || '').slice(0, 3800);
10134
+ Promise.resolve(tg.send(c.chat, body)).catch((e) =>
10135
+ tgLog('ajan dm → telegram hata: ' + String((e && e.message) || e))
10136
+ );
10137
+ } catch {}
10138
+ }
10139
+
10140
+ /* Gelen: Telegram grubu → AJAN DM grubu (+ tüm koşan finance ajanlarını uyandır). */
10141
+ function finTelegramToAgentDm(payload) {
10142
+ try {
10143
+ const body = String((payload && payload.text) || '').trim();
10144
+ if (!body || !engine) return { ok: false };
10145
+ const who = String((payload && (payload.senderName || payload.username)) || 'Telegram');
10146
+ const label = `Sahip · Telegram (${who})`;
10147
+ try {
10148
+ if (typeof engine.agentDmGroupPost === 'function') {
10149
+ engine.agentDmGroupPost({
10150
+ gid: FIN_TEAM_GID,
10151
+ title: FIN_TEAM_TITLE,
10152
+ fromSid: 'tg:' + String((payload && payload.senderId) || ''),
10153
+ fromTitle: label,
10154
+ topic: 'telegram',
10155
+ text: body,
10156
+ viaTelegram: true,
10157
+ });
10158
+ }
10159
+ } catch {}
10160
+ const wake =
10161
+ `[AJAN DM — TELEGRAM GRUBU · ${label}]\n${body}\n` +
10162
+ 'Şimdi yap: sahibin Telegram grubundan gelen bu mesajı değerlendir; gerekiyorsa ekipçe aksiyon al. Kısa rapor ver.';
10163
+ let sent = 0;
10164
+ for (const [sid] of financeState.agents) {
10165
+ if (finWakeAgentDm(sid, wake)) sent += 1;
10166
+ }
10167
+ financeLog(`[telegram] ajan dm grubuna mesaj düştü (${sent} ajan uyandı)`);
10168
+ return { ok: true, sent };
10169
+ } catch {
10170
+ return { ok: false };
10171
+ }
10172
+ }
10173
+
10004
10174
  /* ---- FİNANS OLAY → AJAN DM UYANDIRMA ----
10005
10175
  Stop/TP/bekleyen emir aktivasyonu/fiyat alarmı gibi olaylar koşan finance
10006
10176
  ajanlarına AJAN DM'i olarak düşer ve BOŞTAKİ ajanı TUR SANİYESİNİ
@@ -10031,20 +10201,32 @@ function finWakeAgentDm(sid, text) {
10031
10201
  }
10032
10202
  }
10033
10203
 
10034
- /* Tüm koşan finance ajanlarını olayla uyandır. Dönüş: uyandırılan ajan sayısı. */
10204
+ /* Tüm koşan finance ajanlarını olayla uyandır. Dönüş: uyandırılan ajan sayısı.
10205
+ Olay HER durumda ekip DM grubuna sistem postu olarak düşer (koşan ajan yoksa
10206
+ bile panelde görünür — grup yoksa kurulur). */
10035
10207
  function finWakeAgents(text, meta) {
10036
10208
  const body = String(text || '').trim();
10037
10209
  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
10210
  /* Panel görünürlüğü: olay ekip DM grubuna sistem postu olarak düşer */
10043
10211
  try {
10044
- if (typeof engine._agentTeamPost === 'function') {
10212
+ if (typeof engine.agentDmGroupPost === 'function') {
10213
+ engine.agentDmGroupPost({
10214
+ gid: FIN_TEAM_GID,
10215
+ title: FIN_TEAM_TITLE,
10216
+ fromSid: 'finance',
10217
+ fromTitle: 'Beast Finance',
10218
+ topic: 'ortak görev',
10219
+ text: body,
10220
+ system: true,
10221
+ });
10222
+ } else if (typeof engine._agentTeamPost === 'function') {
10045
10223
  engine._agentTeamPost('team:finance', 'finance', 'Beast Finance', body);
10046
10224
  }
10047
10225
  } catch {}
10226
+ if (!financeState.agents.size) {
10227
+ financeLog('[olay] koşan finance ajanı yok — uyarı yalnız günlük/bildirimde' + (meta && meta.kind ? ' (' + meta.kind + ')' : ''));
10228
+ return 0;
10229
+ }
10048
10230
  let sent = 0;
10049
10231
  for (const [sid] of financeState.agents) {
10050
10232
  if (finWakeAgentDm(sid, body)) sent += 1;
@@ -11473,6 +11655,21 @@ ipcMain.handle('finance:settings', async (_e, patch) => {
11473
11655
  if (p.notifyTrades !== undefined) f.notifyTrades = !!p.notifyTrades;
11474
11656
  if (p.notifyWatchdog !== undefined) f.notifyWatchdog = !!p.notifyWatchdog;
11475
11657
  if (p.notifyTradeOnly !== undefined) f.notifyTradeOnly = !!p.notifyTradeOnly;
11658
+ /* AJAN DM ↔ TELEGRAM köprüsü ayarları (grup SEÇİLMEZ — otomatik bağlanır) */
11659
+ if (p.dmTelegram !== undefined) f.dmTelegram = !!p.dmTelegram;
11660
+ if (p.dmTelegramChat !== undefined) {
11661
+ f.dmTelegramChat = String(p.dmTelegramChat || '').trim().slice(0, 64);
11662
+ if (!f.dmTelegramChat) f.dmTelegramTitle = ''; /* koparıldı → başlık da gitsin */
11663
+ }
11664
+ if (p.dmTelegramTitle !== undefined) f.dmTelegramTitle = String(p.dmTelegramTitle || '').trim().slice(0, 80);
11665
+ /* köprü yeni açıldı ve botun gördüğü TEK grup varsa hemen bağla */
11666
+ if (p.dmTelegram === true && !f.dmTelegramChat) {
11667
+ const known = tgGroupsList();
11668
+ if (known.length === 1) {
11669
+ f.dmTelegramChat = String(known[0].id);
11670
+ f.dmTelegramTitle = String(known[0].title || known[0].id);
11671
+ }
11672
+ }
11476
11673
  if (p.maxDailyLossPct !== undefined) f.maxDailyLossPct = Math.max(0, Math.min(50, Number(p.maxDailyLossPct) || 0));
11477
11674
  if (p.dailyLossAction !== undefined) {
11478
11675
  const v = String(p.dailyLossAction || 'warn').toLowerCase();
@@ -800,7 +800,7 @@
800
800
  </div>
801
801
  <div class="mini-add-row">
802
802
  <input id="wAddCd" class="inp" type="number" min="0" max="10080" value="60" title="soğuma (dk): koşul sürdükçe en fazla bu aralıkta bir bildirir — 0 = yalnız koşul yeniden sağlanınca" style="width:70px" />
803
- <label style="display:flex;align-items:center;gap:5px;font-size:12px;color:var(--muted);white-space:nowrap"><input id="wAddOnce" type="checkbox" /> tek seferlik</label>
803
+ <label style="display:flex;align-items:center;gap:5px;font-size:12px;color:var(--muted);white-space:nowrap"><input id="wAddOnce" type="checkbox" /> tek seferlik <i style="font-style:normal;opacity:.75">(tetiklenince listeden silinir)</i></label>
804
804
  </div>
805
805
  </div>
806
806
  <div id="watchList" class="mini-list"></div>
@@ -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 botu eklediğin Telegram grubuna yazılan ilk mesaj o grubu AJAN DM aynası yapar: AJAN DM'de konuşulanlar (alarmlar dahil) gruba düşer, grupta yazdıkların AJAN DM'de görünür ve finance ajanları uyanır. Grup seçmene gerek yok.">
896
+ <input id="finDmTgOn" type="checkbox" />
897
+ <span class="fsr-main"><b>AJAN DM ↔ TELEGRAM</b><i>Ajan DM grubu Telegram'da canlı — otomatik bağlanır</i></span>
898
+ </label>
899
+ <div id="finDmTgWrap" class="fin-set-sub" hidden>
900
+ <span class="fin-set-lbl">TELEGRAM GRUBU</span>
901
+ <div id="finDmTgState" class="fin-dm-tg-state">—</div>
902
+ <button id="finDmTgUnbind" class="fin-set-btn" title="Bağlantıyı kopar — sonra gruba yazılan ilk mesaj yeni grup olarak otomatik bağlanır">&#x2715;&#xFE0E; Bağlantıyı kopar</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
+ finDmTgState: $('#finDmTgState'),
209
+ finDmTgUnbind: $('#finDmTgUnbind'),
210
+ finDmTgHint: $('#finDmTgHint'),
206
211
  finRiskCard: $('#finRiskCard'),
207
212
  finNotifyCard: $('#finNotifyCard'),
208
213
  finPerfCard: $('#finPerfCard'),
@@ -10119,6 +10124,10 @@ 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 durumu cfg ile senkron kalsın */
10128
+ if (els.finSettingsOverlay && !els.finSettingsOverlay.hidden) {
10129
+ finDmTgPaint();
10130
+ }
10122
10131
  }
10123
10132
 
10124
10133
  function finLogLine(line) {
@@ -10280,8 +10289,34 @@ function finViewApply() {
10280
10289
  if (!finView.risk) finSaveCfg({ watchdog: false });
10281
10290
  }
10282
10291
 
10292
+ /* ---------- AJAN DM ↔ TELEGRAM (görünüm ayarları) ----------
10293
+ Grup SEÇİLMEZ: köprü açıkken botu eklediğin gruba yazılan ilk mesaj o grubu
10294
+ otomatik bağlar (sunucu tarafı: finDmTgBind). Bağlantıyı koparınca yeni
10295
+ grupta ilk mesaj yeniden bağlar. */
10296
+ function finDmTgPaint() {
10297
+ if (!els.finDmTgOn) return;
10298
+ const cfg = finCfgCache || {};
10299
+ const on = cfg.dmTelegram === true;
10300
+ els.finDmTgOn.checked = on;
10301
+ const row = els.finDmTgOn.closest('.fin-set-row');
10302
+ if (row) row.classList.toggle('on', on);
10303
+ if (els.finDmTgWrap) els.finDmTgWrap.hidden = !on;
10304
+ const chat = String(cfg.dmTelegramChat || '');
10305
+ if (els.finDmTgState) {
10306
+ els.finDmTgState.textContent = chat
10307
+ ? '✅ Bağlı: ' + String(cfg.dmTelegramTitle || chat) + ' (' + chat + ')'
10308
+ : 'Bağlı grup yok — botu gruba ekle ve gruba /start yaz; o grup otomatik bağlanır.';
10309
+ }
10310
+ if (els.finDmTgUnbind) els.finDmTgUnbind.hidden = !chat;
10311
+ if (els.finDmTgHint) {
10312
+ els.finDmTgHint.textContent =
10313
+ 'Grupta konuşulan her şey AJAN DM\u2019de görünür; ajanların/alarmların yazdıkları da gruba düşer. Grup değiştirmek için bağlantıyı kopar, yeni grupta /start yaz.';
10314
+ }
10315
+ }
10316
+
10283
10317
  function finSettingsOpen() {
10284
10318
  if (els.finSettingsOverlay) els.finSettingsOverlay.hidden = false;
10319
+ finDmTgPaint();
10285
10320
  }
10286
10321
  function finSettingsCloseModal() {
10287
10322
  if (els.finSettingsOverlay) els.finSettingsOverlay.hidden = true;
@@ -10315,6 +10350,27 @@ if (els.finShowRisk) els.finShowRisk.addEventListener('change', () => finViewTog
10315
10350
  if (els.finShowNotify) els.finShowNotify.addEventListener('change', () => finViewToggle('notify', els.finShowNotify.checked));
10316
10351
  if (els.finShowPerf) els.finShowPerf.addEventListener('change', () => finViewToggle('perf', els.finShowPerf.checked));
10317
10352
  if (els.finShowFlow) els.finShowFlow.addEventListener('change', () => finViewToggle('flow', els.finShowFlow.checked));
10353
+ /* AJAN DM ↔ TELEGRAM köprüsü kontrolleri (grup otomatik bağlanır) */
10354
+ if (els.finDmTgOn) {
10355
+ els.finDmTgOn.addEventListener('change', () => {
10356
+ const on = els.finDmTgOn.checked;
10357
+ if (finCfgCache) finCfgCache.dmTelegram = on;
10358
+ finSaveCfg({ dmTelegram: on });
10359
+ finDmTgPaint();
10360
+ toast(on ? 'AJAN DM ↔ Telegram açıldı — gruba /start yazınca otomatik bağlanır' : 'AJAN DM ↔ Telegram kapatıldı');
10361
+ });
10362
+ }
10363
+ if (els.finDmTgUnbind) {
10364
+ els.finDmTgUnbind.addEventListener('click', () => {
10365
+ if (finCfgCache) {
10366
+ finCfgCache.dmTelegramChat = '';
10367
+ finCfgCache.dmTelegramTitle = '';
10368
+ }
10369
+ finSaveCfg({ dmTelegramChat: '', dmTelegramTitle: '' });
10370
+ finDmTgPaint();
10371
+ toast('Telegram grubu koparıldı — yeni grupta /start yazınca bağlanır');
10372
+ });
10373
+ }
10318
10374
  finViewApply();
10319
10375
 
10320
10376
  /* ---------- 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-dm-tg-state { font-size: 11.5px; color: var(--text); line-height: 1.4; word-break: break-word; }
5886
+ .fin-set-btn {
5887
+ align-self: flex-start;
5888
+ font-size: 10.5px;
5889
+ padding: 3px 8px;
5890
+ cursor: pointer;
5891
+ }