beast-agent 2.57.1 → 2.58.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.57.1",
4
+ "version": "2.58.1",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
@@ -7,6 +7,7 @@
7
7
  - Aynı allow list mantığı: main tarafındaki tgFind() listesindeki kişilere cevap verir */
8
8
 
9
9
  const https = require('https');
10
+ const crypto = require('crypto');
10
11
 
11
12
  const API_BASE = 'https://api.telegram.org/bot';
12
13
  const SEND_CHUNK = 3800; // Telegram mesaj sınırı 4096 — güvenli pay
@@ -30,16 +31,15 @@ class TelegramBridge {
30
31
  this.emit({ type: 'status', status, user: user || null });
31
32
  }
32
33
 
33
- /* Bot API çağrısı — JSON POST, promise sarmalı */
34
- api(method, body = {}, opts = {}) {
34
+ /* Ham POST — JSON ve multipart (görsel) aynı iskeleti kullanır */
35
+ _post(method, payload, headers, timeoutMs) {
35
36
  return new Promise((resolve, reject) => {
36
- const payload = JSON.stringify(body);
37
37
  const req = https.request(
38
38
  `${API_BASE}${this.token}/${method}`,
39
39
  {
40
40
  method: 'POST',
41
- headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) },
42
- timeout: opts.timeout || 15000,
41
+ headers: { ...headers, 'Content-Length': Buffer.byteLength(payload) },
42
+ timeout: timeoutMs || 15000,
43
43
  },
44
44
  (res) => {
45
45
  let data = '';
@@ -64,6 +64,16 @@ class TelegramBridge {
64
64
  });
65
65
  }
66
66
 
67
+ /* Bot API çağrısı — JSON POST, promise sarmalı */
68
+ api(method, body = {}, opts = {}) {
69
+ return this._post(
70
+ method,
71
+ JSON.stringify(body),
72
+ { 'Content-Type': 'application/json' },
73
+ opts.timeout || 15000
74
+ );
75
+ }
76
+
67
77
  async start() {
68
78
  if (!this.token) {
69
79
  this._setStatus('error');
@@ -108,6 +118,8 @@ class TelegramBridge {
108
118
  isGroup: !!(msg.chat && (msg.chat.type === 'group' || msg.chat.type === 'supergroup')),
109
119
  chatTitle: String((msg.chat && (msg.chat.title || msg.chat.username)) || ''),
110
120
  chatType: String((msg.chat && msg.chat.type) || 'private'),
121
+ isForum: !!(msg.chat && msg.chat.is_forum),
122
+ messageThreadId: msg.message_thread_id || null,
111
123
  };
112
124
  try {
113
125
  if (this.onIncoming) this.onIncoming(String(chatId), payload);
@@ -137,8 +149,9 @@ class TelegramBridge {
137
149
  };
138
150
  }
139
151
 
140
- /* Metin gönder — 4096 sınırı için parçalara böl */
141
- async send(chatId, text) {
152
+ /* Metin gönder — 4096 sınırı için parçalara böl; threadId verilirse forum
153
+ konusuna (topic) gönderir (message_thread_id). */
154
+ async send(chatId, text, threadId) {
142
155
  const t = String(text || '');
143
156
  if (!t.trim()) return false;
144
157
  const chunks = [];
@@ -148,10 +161,49 @@ class TelegramBridge {
148
161
  chat_id: chatId,
149
162
  text: part,
150
163
  disable_web_page_preview: true,
164
+ ...(threadId ? { message_thread_id: Number(threadId) } : {}),
151
165
  });
152
166
  }
153
167
  return true;
154
168
  }
169
+
170
+ /* GÖRSEL gönder — data URL (base64) multipart ile yüklenir. PNG/JPG/WEBP
171
+ sendPhoto, GIF sendDocument; caption 1024, dosya 9.5MB üstü atlanır.
172
+ threadId verilirse forum konusuna gider. */
173
+ async sendPhoto(chatId, dataUrl, caption, threadId) {
174
+ try {
175
+ const m = /^data:image\/(png|jpe?g|webp|gif);base64,([A-Za-z0-9+/=\s]+)$/i.exec(String(dataUrl || '').trim());
176
+ if (!m) return false;
177
+ const isJpg = /^jpe?g$/i.test(m[1]);
178
+ const ext = isJpg ? 'jpg' : m[1].toLowerCase();
179
+ const mime = isJpg ? 'image/jpeg' : 'image/' + ext;
180
+ const buf = Buffer.from(m[2].replace(/\s+/g, ''), 'base64');
181
+ if (!buf.length || buf.length > 9.5 * 1024 * 1024) return false;
182
+ const gif = ext === 'gif';
183
+ const method = gif ? 'sendDocument' : 'sendPhoto';
184
+ const field = gif ? 'document' : 'photo';
185
+ const boundary = '----Beast' + crypto.randomBytes(10).toString('hex');
186
+ const parts = [];
187
+ const addField = (name, value) => {
188
+ parts.push(Buffer.from(`--${boundary}\r\nContent-Disposition: form-data; name="${name}"\r\n\r\n${String(value)}\r\n`));
189
+ };
190
+ addField('chat_id', String(chatId));
191
+ if (caption) addField('caption', String(caption).slice(0, 1024));
192
+ if (threadId) addField('message_thread_id', String(Number(threadId)));
193
+ parts.push(
194
+ Buffer.from(
195
+ `--${boundary}\r\nContent-Disposition: form-data; name="${field}"; filename="beast.${ext}"\r\nContent-Type: ${mime}\r\n\r\n`
196
+ )
197
+ );
198
+ parts.push(buf);
199
+ parts.push(Buffer.from('\r\n'));
200
+ parts.push(Buffer.from(`--${boundary}--\r\n`));
201
+ await this._post(method, Buffer.concat(parts), { 'Content-Type': 'multipart/form-data; boundary=' + boundary }, 60000);
202
+ return true;
203
+ } catch {
204
+ return false;
205
+ }
206
+ }
155
207
  }
156
208
 
157
209
  module.exports = { TelegramBridge };
package/src/main.js CHANGED
@@ -2944,6 +2944,7 @@ async function handleTgIncoming(chatId, payload) {
2944
2944
  const allowed = tgFind(payload.senderId, payload.username);
2945
2945
  if (!allowed) {
2946
2946
  tgLog(`skip: grup mesajı chat=${chatId} — gönderici izin listesinde değil`);
2947
+ if (String(dmTg.chat) === String(chatId)) finDmTgNotAllowed(chatId);
2947
2948
  return;
2948
2949
  }
2949
2950
  if (!dmTg.chat) {
@@ -2956,7 +2957,9 @@ async function handleTgIncoming(chatId, payload) {
2956
2957
  return;
2957
2958
  }
2958
2959
  if (String(dmTg.chat) === String(chatId)) {
2959
- finTelegramToAgentDm(payload);
2960
+ /* FORUM: konu (thread) → ilgili AJAN DM grubu; konusuz/genel → finance */
2961
+ const gid = finDmTgGidForThread(payload.messageThreadId);
2962
+ finTelegramToAgentDm(payload, gid);
2960
2963
  return;
2961
2964
  }
2962
2965
  tgLog(`skip: grup mesajı chat=${chatId} (bağlı grup ${dmTg.chat})`);
@@ -9416,12 +9419,14 @@ function finCfg() {
9416
9419
  iptal) düşer; fiyat alarmı, koruma (BE/trailing/kısmi TP), günlük limit ve
9417
9420
  rapor bildirimleri panelde kalır. false = tüm olaylar kanallara gider. */
9418
9421
  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
+ /* AJAN DM ↔ TELEGRAM KÖPRÜSÜ: açıkken AJAN DM gruplarındaki mesajlar seçilen
9423
+ Telegram grubuna düşer; o gruba yazılan mesaj AJAN DM grubunda görünür ve
9424
+ koşan finance ajanları tur beklemeden uyanır. Forum (Konular) destekli
9425
+ grupta her AJAN DM grubu AYRI BAŞLIK (topic) olarak açılır canlı akar. */
9422
9426
  if (typeof f.dmTelegram !== 'boolean') f.dmTelegram = false;
9423
9427
  if (typeof f.dmTelegramChat !== 'string') f.dmTelegramChat = '';
9424
9428
  if (typeof f.dmTelegramTitle !== 'string') f.dmTelegramTitle = '';
9429
+ if (!f.dmTelegramTopics || typeof f.dmTelegramTopics !== 'object') f.dmTelegramTopics = {};
9425
9430
  if (!Number.isFinite(Number(f.maxDailyLossPct))) f.maxDailyLossPct = 3;
9426
9431
  /* GÜNLÜK LİMİT AKSİYONU: warn = yalnız uyarı; stop = tüm finance ajanlarını
9427
9432
  durdur; flatten = durdur + tüm pozisyonları kapat */
@@ -10100,6 +10105,12 @@ function finDmTgBind(chatId, title) {
10100
10105
  if (!id) return false;
10101
10106
  const f = finCfg();
10102
10107
  const t = String(title || '').trim();
10108
+ /* GRUP DEĞİŞTİ: eski konu eşlemeleri yeni chat'te geçersiz — sıfırla */
10109
+ if (String(f.dmTelegramChat || '') !== id) {
10110
+ f.dmTelegramTopics = {};
10111
+ finDmTgThreads.clear();
10112
+ finDmTgBlockedAt.clear();
10113
+ }
10103
10114
  f.dmTelegramChat = id;
10104
10115
  f.dmTelegramTitle = t || f.dmTelegramTitle || id;
10105
10116
  try { saveSettings(); } catch {}
@@ -10111,7 +10122,10 @@ function finDmTgBind(chatId, title) {
10111
10122
  tg.send(
10112
10123
  id,
10113
10124
  '✅ 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.'
10125
+ 'Bundan sonra ajanların/alarmların mesajları buraya düşer; buraya yazdıkların AJAN DM grubunda görünür ve ajanları uyandırır.\n' +
10126
+ 'ÖNEMLİ: Normal mesajlarını görebilmem için beni bu grupta YÖNETİCİ yap (Konuları Yönet izniyle) ya da BotFather → /setprivacy → Disable yap. ' +
10127
+ 'Aksi halde yalnız /komutları ve bana yanıtları görebilirim.\n' +
10128
+ 'Grup Konular (Topics) destekliyorsa her AJAN DM grubu ayrı başlık olarak canlı akar.'
10115
10129
  )
10116
10130
  ).catch(() => {});
10117
10131
  }
@@ -10119,36 +10133,188 @@ function finDmTgBind(chatId, title) {
10119
10133
  return true;
10120
10134
  }
10121
10135
 
10122
- /* Giden: AJAN DM grubu → Telegram (Telegram'dan gelenler geri gönderilmez;
10123
- grup henüz otomatik bağlanmadıysa sessizce beklenir). */
10136
+ /* İzin listesi dışı gönderici bağlı gruba yazdıysa sessiz kalma: 10 dk'da bir
10137
+ gruba kısa uyarı — kullanıcı mesajının neden ajanlara gitmediğini anlasın. */
10138
+ const finDmTgWarnAt = new Map();
10139
+ function finDmTgNotAllowed(chatId) {
10140
+ const id = String(chatId || '');
10141
+ const last = finDmTgWarnAt.get(id) || 0;
10142
+ if (!id || Date.now() - last < 10 * 60 * 1000) return;
10143
+ finDmTgWarnAt.set(id, Date.now());
10144
+ try {
10145
+ Promise.resolve(
10146
+ tg.send(
10147
+ id,
10148
+ '⚠️ Bu Telegram hesabı izin listesinde yok — mesajın ajanlara iletilemedi.\n' +
10149
+ 'Beast → Entegrasyonlar → Telegram bölümünde bu hesabı (ID ya da @kullanıcı adı) izin listesine ekle.'
10150
+ )
10151
+ ).catch(() => {});
10152
+ } catch {}
10153
+ }
10154
+
10155
+ /* ---- TELEGRAM KONU (TOPIC) EŞLEMESİ ----
10156
+ Forum destekli Telegram grubunda her AJAN DM grubu AYRI BAŞLIK olarak açılır:
10157
+ gid → message_thread_id eşlemesi settings.finance.dmTelegramTopics'ta kalıcı.
10158
+ Konu açılamazsa (forum değil / bot yetkisiz) mesajlar grup adı etiketiyle
10159
+ genel akışa düşer — köprü yine çalışır. */
10160
+ const finDmTgThreads = new Map(); // gid -> threadId (>0) | 0 (forum değil — kalıcı)
10161
+ const finDmTgThreadPend = new Map(); // gid -> in-flight açma promise'i
10162
+ const finDmTgBlockedAt = new Map(); // gid -> son hata ts (geçici: 10 dk sonra yeniden dener)
10163
+ const FIN_DM_TG_RETRY_MS = 10 * 60 * 1000;
10164
+ let finDmTgChatMeta = { id: '', at: 0, isForum: false };
10165
+ let finDmTgSendChain = Promise.resolve(); // gönderim sırası korunsun
10166
+
10167
+ function finDmTgChatInfo(chatId) {
10168
+ const id = String(chatId || '');
10169
+ if (finDmTgChatMeta.id === id && Date.now() - finDmTgChatMeta.at < 5 * 60000) {
10170
+ return Promise.resolve(finDmTgChatMeta);
10171
+ }
10172
+ return Promise.resolve(tg.api('getChat', { chat_id: id }))
10173
+ .then((r) => {
10174
+ finDmTgChatMeta = { id, at: Date.now(), isForum: !!(r && r.is_forum) };
10175
+ return finDmTgChatMeta;
10176
+ })
10177
+ .catch(() => {
10178
+ finDmTgChatMeta = { id, at: Date.now(), isForum: false };
10179
+ return finDmTgChatMeta;
10180
+ });
10181
+ }
10182
+
10183
+ function finDmTgThreadCached(gid) {
10184
+ if (finDmTgThreads.has(gid)) return Number(finDmTgThreads.get(gid)) || 0;
10185
+ const f = finCfg();
10186
+ const rec = f.dmTelegramTopics && f.dmTelegramTopics[gid];
10187
+ const tid = rec && rec.threadId ? Number(rec.threadId) || 0 : 0;
10188
+ if (tid) finDmTgThreads.set(gid, tid);
10189
+ return tid;
10190
+ }
10191
+
10192
+ /* gid için topic'i hazırla (yoksa aç). Dönüş: threadId (0 = konu yok/genel akış) */
10193
+ function finDmTgThreadEnsure(chatId, gid, title) {
10194
+ const cached = finDmTgThreadCached(gid);
10195
+ if (cached > 0) return Promise.resolve(cached);
10196
+ if (finDmTgThreads.has(gid)) return Promise.resolve(0); // forum değil → genel akış
10197
+ const blockedAt = finDmTgBlockedAt.get(gid) || 0;
10198
+ if (blockedAt && Date.now() - blockedAt < FIN_DM_TG_RETRY_MS) return Promise.resolve(0);
10199
+ if (finDmTgThreadPend.has(gid)) return finDmTgThreadPend.get(gid);
10200
+ const p = (async () => {
10201
+ try {
10202
+ const info = await finDmTgChatInfo(chatId);
10203
+ if (!info.isForum) { finDmTgThreads.set(gid, 0); return 0; }
10204
+ const r = await tg.api('createForumTopic', {
10205
+ chat_id: chatId,
10206
+ name: String(title || gid).slice(0, 128),
10207
+ });
10208
+ const tid = Number(r && r.message_thread_id) || 0;
10209
+ if (!tid) { finDmTgBlockedAt.set(gid, Date.now()); return 0; }
10210
+ finDmTgThreads.set(gid, tid);
10211
+ finDmTgBlockedAt.delete(gid);
10212
+ const f = finCfg();
10213
+ if (!f.dmTelegramTopics || typeof f.dmTelegramTopics !== 'object') f.dmTelegramTopics = {};
10214
+ f.dmTelegramTopics[gid] = { threadId: tid, title: String(title || gid), at: Date.now() };
10215
+ try { saveSettings(); } catch {}
10216
+ tgLog(`telegram konu açıldı: "${title}" (${gid}) → thread ${tid}`);
10217
+ return tid;
10218
+ } catch (e) {
10219
+ /* geçici hata (ağ/yetki): kalıcı işaretleme — 10 dk sonra yeniden denenir */
10220
+ finDmTgBlockedAt.set(gid, Date.now());
10221
+ tgLog(`telegram konu açılamadı (${gid}): ` + String((e && e.message) || e));
10222
+ return 0;
10223
+ } finally {
10224
+ finDmTgThreadPend.delete(gid);
10225
+ }
10226
+ })();
10227
+ finDmTgThreadPend.set(gid, p);
10228
+ return p;
10229
+ }
10230
+
10231
+ /* message_thread_id → AJAN DM gid (bilinmeyen konu: genel akış → finance) */
10232
+ function finDmTgGidForThread(threadId) {
10233
+ const tid = Number(threadId) || 0;
10234
+ if (!tid) return '';
10235
+ const f = finCfg();
10236
+ const topics = f.dmTelegramTopics && typeof f.dmTelegramTopics === 'object' ? f.dmTelegramTopics : {};
10237
+ for (const [gid, rec] of Object.entries(topics)) {
10238
+ if (rec && Number(rec.threadId) === tid) return gid;
10239
+ }
10240
+ return '';
10241
+ }
10242
+
10243
+ /* Gönderimleri SIRAYLA işle: konu açma + mesaj sırası bozulmasın. */
10244
+ function finDmTgQueue(task) {
10245
+ finDmTgSendChain = finDmTgSendChain
10246
+ .then(() => task())
10247
+ .catch((e) => tgLog('ajan dm → telegram kuyruk hata: ' + String((e && e.message) || e)));
10248
+ return finDmTgSendChain;
10249
+ }
10250
+
10251
+ /* Giden: HERHANGİ bir AJAN DM grubu → Telegram (topic varsa kendi başlığına;
10252
+ Telegram'dan gelenler geri gönderilmez; grup bağlanmadıysa beklenir). */
10124
10253
  function finAgentDmToTelegram(dm) {
10125
10254
  try {
10126
10255
  if (!dm || dm.viaTelegram) return;
10127
- if (String(dm.group || '') !== FIN_TEAM_GID) return;
10256
+ const gid = String(dm.group || '');
10257
+ if (!gid) return; /* yalnız grup sohbetleri köprülenir (1:1 DM panelde kalır) */
10128
10258
  const c = finDmTgCfg();
10129
10259
  if (!c.on || !c.chat || !tg || !tg.connected) return;
10260
+ const title = String(dm.groupTitle || (gid === FIN_TEAM_GID ? FIN_TEAM_TITLE : gid));
10130
10261
  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
- );
10262
+ const text = String(dm.text || '').slice(0, 3800);
10263
+ const img = String(dm.image || '');
10264
+ const hasImg = /^data:image\//i.test(img);
10265
+ finDmTgQueue(async () => {
10266
+ const tid = await finDmTgThreadEnsure(c.chat, gid, title);
10267
+ const target = tid > 0 ? Number(tid) : undefined;
10268
+ const full = `${who}:\n${text}`;
10269
+ /* GÖRSEL: Telegram'a FOTO olarak gider (caption = gönderen + metin);
10270
+ metnin caption'a sığmayan kalanı ayrı mesajla gider. */
10271
+ if (hasImg) {
10272
+ const cap = full.slice(0, 1000);
10273
+ let ok = false;
10274
+ try { ok = await tg.sendPhoto(c.chat, img, cap, target); } catch { ok = false; }
10275
+ if (ok) {
10276
+ const rest = full.slice(1000);
10277
+ if (rest.trim()) await tg.send(c.chat, '…' + rest, target).catch(() => {});
10278
+ return;
10279
+ }
10280
+ /* foto gönderilemedi (bozuk/büyük) → metne düş, görsel notuyla */
10281
+ }
10282
+ const head = hasImg ? `${who}: (görsel gönderilemedi)\n${text}` : full;
10283
+ if (target) {
10284
+ try {
10285
+ await tg.send(c.chat, head, target);
10286
+ return;
10287
+ } catch {
10288
+ /* konu silinmiş/erişim yok → genel akışa düş (mesaj kaybolmasın) */
10289
+ }
10290
+ }
10291
+ await tg.send(c.chat, `👥 [${title}] ${head}`);
10292
+ });
10137
10293
  } catch {}
10138
10294
  }
10139
10295
 
10140
- /* Gelen: Telegram grubu → AJAN DM grubu (+ tüm koşan finance ajanlarını uyandır). */
10141
- function finTelegramToAgentDm(payload) {
10296
+ /* Gelen: Telegram grubu/konusu → ilgili AJAN DM grubu (+ üye ajanları uyandır).
10297
+ gid verilmezse finance grubuna düşer (genel akış / konusuz mesaj). */
10298
+ function finTelegramToAgentDm(payload, gid) {
10142
10299
  try {
10143
10300
  const body = String((payload && payload.text) || '').trim();
10144
10301
  if (!body || !engine) return { ok: false };
10302
+ const key = String(gid || FIN_TEAM_GID);
10303
+ const f = finCfg();
10304
+ const topics = f.dmTelegramTopics && typeof f.dmTelegramTopics === 'object' ? f.dmTelegramTopics : {};
10305
+ const title = String(
10306
+ (topics[key] && topics[key].title) ||
10307
+ (key === FIN_TEAM_GID ? FIN_TEAM_TITLE : '') ||
10308
+ (payload && payload.chatTitle) ||
10309
+ key
10310
+ );
10145
10311
  const who = String((payload && (payload.senderName || payload.username)) || 'Telegram');
10146
10312
  const label = `Sahip · Telegram (${who})`;
10147
10313
  try {
10148
10314
  if (typeof engine.agentDmGroupPost === 'function') {
10149
10315
  engine.agentDmGroupPost({
10150
- gid: FIN_TEAM_GID,
10151
- title: FIN_TEAM_TITLE,
10316
+ gid: key,
10317
+ title,
10152
10318
  fromSid: 'tg:' + String((payload && payload.senderId) || ''),
10153
10319
  fromTitle: label,
10154
10320
  topic: 'telegram',
@@ -10157,14 +10323,31 @@ function finTelegramToAgentDm(payload) {
10157
10323
  });
10158
10324
  }
10159
10325
  } catch {}
10326
+ /* Uyandırma: grubun KOŞAN üyeleri; finance grubu için tüm finance ajanları */
10327
+ const g = engine._agentGroups && engine._agentGroups.get(key);
10328
+ const members = g && Array.isArray(g.members) ? g.members.map(String) : [];
10160
10329
  const wake =
10161
- `[AJAN DM — TELEGRAM GRUBU · ${label}]\n${body}\n` +
10330
+ `[AJAN DM — TELEGRAM${title ? ' · ' + title : ''} · ${label}]\n${body}\n` +
10162
10331
  'Şimdi yap: sahibin Telegram grubundan gelen bu mesajı değerlendir; gerekiyorsa ekipçe aksiyon al. Kısa rapor ver.';
10163
10332
  let sent = 0;
10164
10333
  for (const [sid] of financeState.agents) {
10334
+ if (key !== FIN_TEAM_GID && members.length && !members.includes(String(sid))) continue;
10165
10335
  if (finWakeAgentDm(sid, wake)) sent += 1;
10166
10336
  }
10167
- financeLog(`[telegram] ajan dm grubuna mesaj düştü (${sent} ajan uyandı)`);
10337
+ /* finance dışı koşan üyeler (paralel ajan grupları) da mesajı görsün */
10338
+ const jobs = engine._bgJobs || new Map();
10339
+ for (const m of members) {
10340
+ const sid = String(m);
10341
+ if (financeState.agents.has(sid)) continue;
10342
+ const j = jobs.get(sid);
10343
+ if (!j || j.status !== 'running') continue;
10344
+ try {
10345
+ (engine._pendingReports = engine._pendingReports || []).push({ parentId: sid, text: wake, dm: true, wake: true });
10346
+ engine.flushPendingReports(sid);
10347
+ sent += 1;
10348
+ } catch {}
10349
+ }
10350
+ financeLog(`[telegram] ajan dm grubuna mesaj düştü: ${title} (${sent} ajan uyandı)`);
10168
10351
  return { ok: true, sent };
10169
10352
  } catch {
10170
10353
  return { ok: false };
@@ -11658,8 +11841,15 @@ ipcMain.handle('finance:settings', async (_e, patch) => {
11658
11841
  /* AJAN DM ↔ TELEGRAM köprüsü ayarları (grup SEÇİLMEZ — otomatik bağlanır) */
11659
11842
  if (p.dmTelegram !== undefined) f.dmTelegram = !!p.dmTelegram;
11660
11843
  if (p.dmTelegramChat !== undefined) {
11844
+ const prevChat = String(f.dmTelegramChat || '');
11661
11845
  f.dmTelegramChat = String(p.dmTelegramChat || '').trim().slice(0, 64);
11662
11846
  if (!f.dmTelegramChat) f.dmTelegramTitle = ''; /* koparıldı → başlık da gitsin */
11847
+ /* grup değişti/koparıldı: eski konu eşlemeleri geçersiz — sıfırla */
11848
+ if (f.dmTelegramChat !== prevChat) {
11849
+ f.dmTelegramTopics = {};
11850
+ finDmTgThreads.clear();
11851
+ finDmTgBlockedAt.clear();
11852
+ }
11663
11853
  }
11664
11854
  if (p.dmTelegramTitle !== undefined) f.dmTelegramTitle = String(p.dmTelegramTitle || '').trim().slice(0, 80);
11665
11855
  /* köprü yeni açıldı ve botun gördüğü TEK grup varsa hemen bağla */
@@ -892,9 +892,9 @@
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
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.">
895
+ <label class="fin-set-row" title="Açıkken botu eklediğin Telegram grubuna yazılan ilk mesaj o grubu AJAN DM aynası yapar. Her AJAN DM grubu Telegram'da AYRI BAŞLIK (topic) olarak canlı akar; başlığa yazdıkların ilgili AJAN DM grubunda görünür ve o grubun ajanları uyanır. Grup/Konu seçmene gerek yok.">
896
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>
897
+ <span class="fsr-main"><b>AJAN DM ↔ TELEGRAM</b><i>Her ajan grubu Telegram'da ayrı başlık — canlı</i></span>
898
898
  </label>
899
899
  <div id="finDmTgWrap" class="fin-set-sub" hidden>
900
900
  <span class="fin-set-lbl">TELEGRAM GRUBU</span>
@@ -10310,7 +10310,9 @@ function finDmTgPaint() {
10310
10310
  if (els.finDmTgUnbind) els.finDmTgUnbind.hidden = !chat;
10311
10311
  if (els.finDmTgHint) {
10312
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.';
10313
+ 'Her AJAN DM grubu (Beast Finance EKİP, sembol ekipleri vb.) Telegram\u2019da AYRI BAŞLIK (topic) olarak canlı akar. ' +
10314
+ 'Telegram grubunda Konular (Topics) açık ve bot YÖNETİCİ (Konuları Yönet) olmalı — yönetici değilse normal mesajlarını bot göremez (BotFather → /setprivacy → Disable da olur); Konular kapalıysa mesajlar tek akışa grup etiketiyle düşer. ' +
10315
+ 'Bir başlığa yazdığın mesaj ilgili AJAN DM grubunda görünür ve o grubun ajanları uyanır.';
10314
10316
  }
10315
10317
  }
10316
10318