beast-agent 2.57.0 → 2.58.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.57.0",
4
+ "version": "2.58.0",
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
  },
@@ -108,6 +108,8 @@ class TelegramBridge {
108
108
  isGroup: !!(msg.chat && (msg.chat.type === 'group' || msg.chat.type === 'supergroup')),
109
109
  chatTitle: String((msg.chat && (msg.chat.title || msg.chat.username)) || ''),
110
110
  chatType: String((msg.chat && msg.chat.type) || 'private'),
111
+ isForum: !!(msg.chat && msg.chat.is_forum),
112
+ messageThreadId: msg.message_thread_id || null,
111
113
  };
112
114
  try {
113
115
  if (this.onIncoming) this.onIncoming(String(chatId), payload);
@@ -137,8 +139,9 @@ class TelegramBridge {
137
139
  };
138
140
  }
139
141
 
140
- /* Metin gönder — 4096 sınırı için parçalara böl */
141
- async send(chatId, text) {
142
+ /* Metin gönder — 4096 sınırı için parçalara böl; threadId verilirse forum
143
+ konusuna (topic) gönderir (message_thread_id). */
144
+ async send(chatId, text, threadId) {
142
145
  const t = String(text || '');
143
146
  if (!t.trim()) return false;
144
147
  const chunks = [];
@@ -148,6 +151,7 @@ class TelegramBridge {
148
151
  chat_id: chatId,
149
152
  text: part,
150
153
  disable_web_page_preview: true,
154
+ ...(threadId ? { message_thread_id: Number(threadId) } : {}),
151
155
  });
152
156
  }
153
157
  return true;
@@ -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
@@ -2931,20 +2931,37 @@ async function handleTgIncoming(chatId, payload) {
2931
2931
  try {
2932
2932
  if (!engine) return;
2933
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. */
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. */
2935
2937
  if (payload.isGroup) {
2936
2938
  tgRememberGroup(chatId, payload.chatTitle);
2937
2939
  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 */
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;
2944
2955
  finTelegramToAgentDm(payload);
2945
2956
  return;
2946
2957
  }
2947
- tgLog(`skip: grup mesajı chat=${chatId} (grup desteği kapalı)`);
2958
+ if (String(dmTg.chat) === String(chatId)) {
2959
+ /* FORUM: konu (thread) → ilgili AJAN DM grubu; konusuz/genel → finance */
2960
+ const gid = finDmTgGidForThread(payload.messageThreadId);
2961
+ finTelegramToAgentDm(payload, gid);
2962
+ return;
2963
+ }
2964
+ tgLog(`skip: grup mesajı chat=${chatId} (bağlı grup ${dmTg.chat})`);
2948
2965
  return;
2949
2966
  }
2950
2967
  const hit = tgFind(payload.senderId, payload.username);
@@ -9401,12 +9418,14 @@ function finCfg() {
9401
9418
  iptal) düşer; fiyat alarmı, koruma (BE/trailing/kısmi TP), günlük limit ve
9402
9419
  rapor bildirimleri panelde kalır. false = tüm olaylar kanallara gider. */
9403
9420
  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. */
9421
+ /* AJAN DM ↔ TELEGRAM KÖPRÜSÜ: açıkken AJAN DM gruplarındaki mesajlar seçilen
9422
+ Telegram grubuna düşer; o gruba yazılan mesaj AJAN DM grubunda görünür ve
9423
+ koşan finance ajanları tur beklemeden uyanır. Forum (Konular) destekli
9424
+ grupta her AJAN DM grubu AYRI BAŞLIK (topic) olarak açılır canlı akar. */
9407
9425
  if (typeof f.dmTelegram !== 'boolean') f.dmTelegram = false;
9408
9426
  if (typeof f.dmTelegramChat !== 'string') f.dmTelegramChat = '';
9409
9427
  if (typeof f.dmTelegramTitle !== 'string') f.dmTelegramTitle = '';
9428
+ if (!f.dmTelegramTopics || typeof f.dmTelegramTopics !== 'object') f.dmTelegramTopics = {};
9410
9429
  if (!Number.isFinite(Number(f.maxDailyLossPct))) f.maxDailyLossPct = 3;
9411
9430
  /* GÜNLÜK LİMİT AKSİYONU: warn = yalnız uyarı; stop = tüm finance ajanlarını
9412
9431
  durdur; flatten = durdur + tüm pozisyonları kapat */
@@ -10067,46 +10086,196 @@ async function finStatsRefresh(force) {
10067
10086
  const FIN_TEAM_GID = 'team:finance';
10068
10087
  const FIN_TEAM_TITLE = 'Beast Finance EKİP';
10069
10088
 
10070
- /* Köprü ayarı (tek nokta): açık + grup seçili mi? */
10089
+ /* Köprü ayarı: açık mı + bağlı grup (chatId). Grup SEÇİLMEZ — köprü açıkken
10090
+ gruba yazılan ilk mesaj o grubu otomatik bağlar (finDmTgBind). */
10071
10091
  function finDmTgCfg() {
10072
10092
  let f = {};
10073
10093
  try { f = finCfg(); } catch {}
10074
10094
  return {
10075
- on: !!(f && f.dmTelegram && f.dmTelegramChat),
10095
+ on: !!(f && f.dmTelegram),
10076
10096
  chat: String((f && f.dmTelegramChat) || ''),
10077
10097
  title: String((f && f.dmTelegramTitle) || ''),
10078
10098
  };
10079
10099
  }
10080
10100
 
10081
- /* Giden: AJAN DM grubu → Telegram (Telegram'dan gelenler geri gönderilmez). */
10101
+ /* Otomatik bağlama: gelen grup mesajı (izinli gönderici) AJAN DM aynası olur. */
10102
+ function finDmTgBind(chatId, title) {
10103
+ const id = String(chatId || '');
10104
+ if (!id) return false;
10105
+ const f = finCfg();
10106
+ const t = String(title || '').trim();
10107
+ /* GRUP DEĞİŞTİ: eski konu eşlemeleri yeni chat'te geçersiz — sıfırla */
10108
+ if (String(f.dmTelegramChat || '') !== id) {
10109
+ f.dmTelegramTopics = {};
10110
+ finDmTgThreads.clear();
10111
+ finDmTgBlockedAt.clear();
10112
+ }
10113
+ f.dmTelegramChat = id;
10114
+ f.dmTelegramTitle = t || f.dmTelegramTitle || id;
10115
+ try { saveSettings(); } catch {}
10116
+ tgLog(`ajan dm telegram grubu OTOMATİK bağlandı: ${id}${t ? ' (' + t + ')' : ''}`);
10117
+ /* gruba onay: kullanıcı bağlandığını net görsün */
10118
+ try {
10119
+ if (tg && tg.connected) {
10120
+ Promise.resolve(
10121
+ tg.send(
10122
+ id,
10123
+ '✅ Beast Finance AJAN DM bu gruba bağlandı.\n' +
10124
+ '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' +
10125
+ 'NOT: Grup Konular (Topics) destekliyorsa her AJAN DM grubu ayrı başlık olarak canlı akar — botu yönetici yapıp Konuları Yönet izni ver.'
10126
+ )
10127
+ ).catch(() => {});
10128
+ }
10129
+ } catch {}
10130
+ return true;
10131
+ }
10132
+
10133
+ /* ---- TELEGRAM KONU (TOPIC) EŞLEMESİ ----
10134
+ Forum destekli Telegram grubunda her AJAN DM grubu AYRI BAŞLIK olarak açılır:
10135
+ gid → message_thread_id eşlemesi settings.finance.dmTelegramTopics'ta kalıcı.
10136
+ Konu açılamazsa (forum değil / bot yetkisiz) mesajlar grup adı etiketiyle
10137
+ genel akışa düşer — köprü yine çalışır. */
10138
+ const finDmTgThreads = new Map(); // gid -> threadId (>0) | 0 (forum değil — kalıcı)
10139
+ const finDmTgThreadPend = new Map(); // gid -> in-flight açma promise'i
10140
+ const finDmTgBlockedAt = new Map(); // gid -> son hata ts (geçici: 10 dk sonra yeniden dener)
10141
+ const FIN_DM_TG_RETRY_MS = 10 * 60 * 1000;
10142
+ let finDmTgChatMeta = { id: '', at: 0, isForum: false };
10143
+ let finDmTgSendChain = Promise.resolve(); // gönderim sırası korunsun
10144
+
10145
+ function finDmTgChatInfo(chatId) {
10146
+ const id = String(chatId || '');
10147
+ if (finDmTgChatMeta.id === id && Date.now() - finDmTgChatMeta.at < 5 * 60000) {
10148
+ return Promise.resolve(finDmTgChatMeta);
10149
+ }
10150
+ return Promise.resolve(tg.api('getChat', { chat_id: id }))
10151
+ .then((r) => {
10152
+ finDmTgChatMeta = { id, at: Date.now(), isForum: !!(r && r.is_forum) };
10153
+ return finDmTgChatMeta;
10154
+ })
10155
+ .catch(() => {
10156
+ finDmTgChatMeta = { id, at: Date.now(), isForum: false };
10157
+ return finDmTgChatMeta;
10158
+ });
10159
+ }
10160
+
10161
+ function finDmTgThreadCached(gid) {
10162
+ if (finDmTgThreads.has(gid)) return Number(finDmTgThreads.get(gid)) || 0;
10163
+ const f = finCfg();
10164
+ const rec = f.dmTelegramTopics && f.dmTelegramTopics[gid];
10165
+ const tid = rec && rec.threadId ? Number(rec.threadId) || 0 : 0;
10166
+ if (tid) finDmTgThreads.set(gid, tid);
10167
+ return tid;
10168
+ }
10169
+
10170
+ /* gid için topic'i hazırla (yoksa aç). Dönüş: threadId (0 = konu yok/genel akış) */
10171
+ function finDmTgThreadEnsure(chatId, gid, title) {
10172
+ const cached = finDmTgThreadCached(gid);
10173
+ if (cached > 0) return Promise.resolve(cached);
10174
+ if (finDmTgThreads.has(gid)) return Promise.resolve(0); // forum değil → genel akış
10175
+ const blockedAt = finDmTgBlockedAt.get(gid) || 0;
10176
+ if (blockedAt && Date.now() - blockedAt < FIN_DM_TG_RETRY_MS) return Promise.resolve(0);
10177
+ if (finDmTgThreadPend.has(gid)) return finDmTgThreadPend.get(gid);
10178
+ const p = (async () => {
10179
+ try {
10180
+ const info = await finDmTgChatInfo(chatId);
10181
+ if (!info.isForum) { finDmTgThreads.set(gid, 0); return 0; }
10182
+ const r = await tg.api('createForumTopic', {
10183
+ chat_id: chatId,
10184
+ name: String(title || gid).slice(0, 128),
10185
+ });
10186
+ const tid = Number(r && r.message_thread_id) || 0;
10187
+ if (!tid) { finDmTgBlockedAt.set(gid, Date.now()); return 0; }
10188
+ finDmTgThreads.set(gid, tid);
10189
+ finDmTgBlockedAt.delete(gid);
10190
+ const f = finCfg();
10191
+ if (!f.dmTelegramTopics || typeof f.dmTelegramTopics !== 'object') f.dmTelegramTopics = {};
10192
+ f.dmTelegramTopics[gid] = { threadId: tid, title: String(title || gid), at: Date.now() };
10193
+ try { saveSettings(); } catch {}
10194
+ tgLog(`telegram konu açıldı: "${title}" (${gid}) → thread ${tid}`);
10195
+ return tid;
10196
+ } catch (e) {
10197
+ /* geçici hata (ağ/yetki): kalıcı işaretleme — 10 dk sonra yeniden denenir */
10198
+ finDmTgBlockedAt.set(gid, Date.now());
10199
+ tgLog(`telegram konu açılamadı (${gid}): ` + String((e && e.message) || e));
10200
+ return 0;
10201
+ } finally {
10202
+ finDmTgThreadPend.delete(gid);
10203
+ }
10204
+ })();
10205
+ finDmTgThreadPend.set(gid, p);
10206
+ return p;
10207
+ }
10208
+
10209
+ /* message_thread_id → AJAN DM gid (bilinmeyen konu: genel akış → finance) */
10210
+ function finDmTgGidForThread(threadId) {
10211
+ const tid = Number(threadId) || 0;
10212
+ if (!tid) return '';
10213
+ const f = finCfg();
10214
+ const topics = f.dmTelegramTopics && typeof f.dmTelegramTopics === 'object' ? f.dmTelegramTopics : {};
10215
+ for (const [gid, rec] of Object.entries(topics)) {
10216
+ if (rec && Number(rec.threadId) === tid) return gid;
10217
+ }
10218
+ return '';
10219
+ }
10220
+
10221
+ /* Gönderimleri SIRAYLA işle: konu açma + mesaj sırası bozulmasın. */
10222
+ function finDmTgQueue(task) {
10223
+ finDmTgSendChain = finDmTgSendChain
10224
+ .then(() => task())
10225
+ .catch((e) => tgLog('ajan dm → telegram kuyruk hata: ' + String((e && e.message) || e)));
10226
+ return finDmTgSendChain;
10227
+ }
10228
+
10229
+ /* Giden: HERHANGİ bir AJAN DM grubu → Telegram (topic varsa kendi başlığına;
10230
+ Telegram'dan gelenler geri gönderilmez; grup bağlanmadıysa beklenir). */
10082
10231
  function finAgentDmToTelegram(dm) {
10083
10232
  try {
10084
10233
  if (!dm || dm.viaTelegram) return;
10085
- if (String(dm.group || '') !== FIN_TEAM_GID) return;
10234
+ const gid = String(dm.group || '');
10235
+ if (!gid) return; /* yalnız grup sohbetleri köprülenir (1:1 DM panelde kalır) */
10086
10236
  const c = finDmTgCfg();
10087
- if (!c.on || !tg || !tg.connected) return;
10237
+ if (!c.on || !c.chat || !tg || !tg.connected) return;
10238
+ const title = String(dm.groupTitle || (gid === FIN_TEAM_GID ? FIN_TEAM_TITLE : gid));
10088
10239
  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
- );
10240
+ const text = String(dm.text || '').slice(0, 3800);
10241
+ finDmTgQueue(async () => {
10242
+ const tid = await finDmTgThreadEnsure(c.chat, gid, title);
10243
+ const plain = `👥 [${title}] ${who}:\n${text}`;
10244
+ if (tid > 0) {
10245
+ try {
10246
+ await tg.send(c.chat, `${who}:\n${text}`, tid);
10247
+ return;
10248
+ } catch {
10249
+ /* konu silinmiş/erişim yok → genel akışa düş (mesaj kaybolmasın) */
10250
+ }
10251
+ }
10252
+ await tg.send(c.chat, plain);
10253
+ });
10095
10254
  } catch {}
10096
10255
  }
10097
10256
 
10098
- /* Gelen: Telegram grubu → AJAN DM grubu (+ tüm koşan finance ajanlarını uyandır). */
10099
- function finTelegramToAgentDm(payload) {
10257
+ /* Gelen: Telegram grubu/konusu → ilgili AJAN DM grubu (+ üye ajanları uyandır).
10258
+ gid verilmezse finance grubuna düşer (genel akış / konusuz mesaj). */
10259
+ function finTelegramToAgentDm(payload, gid) {
10100
10260
  try {
10101
10261
  const body = String((payload && payload.text) || '').trim();
10102
10262
  if (!body || !engine) return { ok: false };
10263
+ const key = String(gid || FIN_TEAM_GID);
10264
+ const f = finCfg();
10265
+ const topics = f.dmTelegramTopics && typeof f.dmTelegramTopics === 'object' ? f.dmTelegramTopics : {};
10266
+ const title = String(
10267
+ (topics[key] && topics[key].title) ||
10268
+ (key === FIN_TEAM_GID ? FIN_TEAM_TITLE : '') ||
10269
+ (payload && payload.chatTitle) ||
10270
+ key
10271
+ );
10103
10272
  const who = String((payload && (payload.senderName || payload.username)) || 'Telegram');
10104
10273
  const label = `Sahip · Telegram (${who})`;
10105
10274
  try {
10106
10275
  if (typeof engine.agentDmGroupPost === 'function') {
10107
10276
  engine.agentDmGroupPost({
10108
- gid: FIN_TEAM_GID,
10109
- title: FIN_TEAM_TITLE,
10277
+ gid: key,
10278
+ title,
10110
10279
  fromSid: 'tg:' + String((payload && payload.senderId) || ''),
10111
10280
  fromTitle: label,
10112
10281
  topic: 'telegram',
@@ -10115,14 +10284,31 @@ function finTelegramToAgentDm(payload) {
10115
10284
  });
10116
10285
  }
10117
10286
  } catch {}
10287
+ /* Uyandırma: grubun KOŞAN üyeleri; finance grubu için tüm finance ajanları */
10288
+ const g = engine._agentGroups && engine._agentGroups.get(key);
10289
+ const members = g && Array.isArray(g.members) ? g.members.map(String) : [];
10118
10290
  const wake =
10119
- `[AJAN DM — TELEGRAM GRUBU · ${label}]\n${body}\n` +
10291
+ `[AJAN DM — TELEGRAM${title ? ' · ' + title : ''} · ${label}]\n${body}\n` +
10120
10292
  'Şimdi yap: sahibin Telegram grubundan gelen bu mesajı değerlendir; gerekiyorsa ekipçe aksiyon al. Kısa rapor ver.';
10121
10293
  let sent = 0;
10122
10294
  for (const [sid] of financeState.agents) {
10295
+ if (key !== FIN_TEAM_GID && members.length && !members.includes(String(sid))) continue;
10123
10296
  if (finWakeAgentDm(sid, wake)) sent += 1;
10124
10297
  }
10125
- financeLog(`[telegram] ajan dm grubuna mesaj düştü (${sent} ajan uyandı)`);
10298
+ /* finance dışı koşan üyeler (paralel ajan grupları) da mesajı görsün */
10299
+ const jobs = engine._bgJobs || new Map();
10300
+ for (const m of members) {
10301
+ const sid = String(m);
10302
+ if (financeState.agents.has(sid)) continue;
10303
+ const j = jobs.get(sid);
10304
+ if (!j || j.status !== 'running') continue;
10305
+ try {
10306
+ (engine._pendingReports = engine._pendingReports || []).push({ parentId: sid, text: wake, dm: true, wake: true });
10307
+ engine.flushPendingReports(sid);
10308
+ sent += 1;
10309
+ } catch {}
10310
+ }
10311
+ financeLog(`[telegram] ajan dm grubuna mesaj düştü: ${title} (${sent} ajan uyandı)`);
10126
10312
  return { ok: true, sent };
10127
10313
  } catch {
10128
10314
  return { ok: false };
@@ -11613,13 +11799,28 @@ ipcMain.handle('finance:settings', async (_e, patch) => {
11613
11799
  if (p.notifyTrades !== undefined) f.notifyTrades = !!p.notifyTrades;
11614
11800
  if (p.notifyWatchdog !== undefined) f.notifyWatchdog = !!p.notifyWatchdog;
11615
11801
  if (p.notifyTradeOnly !== undefined) f.notifyTradeOnly = !!p.notifyTradeOnly;
11616
- /* AJAN DM ↔ TELEGRAM köprüsü ayarları */
11802
+ /* AJAN DM ↔ TELEGRAM köprüsü ayarları (grup SEÇİLMEZ — otomatik bağlanır) */
11617
11803
  if (p.dmTelegram !== undefined) f.dmTelegram = !!p.dmTelegram;
11618
11804
  if (p.dmTelegramChat !== undefined) {
11805
+ const prevChat = String(f.dmTelegramChat || '');
11619
11806
  f.dmTelegramChat = String(p.dmTelegramChat || '').trim().slice(0, 64);
11620
- if (!f.dmTelegramChat) f.dmTelegram = false;
11807
+ if (!f.dmTelegramChat) f.dmTelegramTitle = ''; /* koparıldı → başlık da gitsin */
11808
+ /* grup değişti/koparıldı: eski konu eşlemeleri geçersiz — sıfırla */
11809
+ if (f.dmTelegramChat !== prevChat) {
11810
+ f.dmTelegramTopics = {};
11811
+ finDmTgThreads.clear();
11812
+ finDmTgBlockedAt.clear();
11813
+ }
11621
11814
  }
11622
11815
  if (p.dmTelegramTitle !== undefined) f.dmTelegramTitle = String(p.dmTelegramTitle || '').trim().slice(0, 80);
11816
+ /* köprü yeni açıldı ve botun gördüğü TEK grup varsa hemen bağla */
11817
+ if (p.dmTelegram === true && !f.dmTelegramChat) {
11818
+ const known = tgGroupsList();
11819
+ if (known.length === 1) {
11820
+ f.dmTelegramChat = String(known[0].id);
11821
+ f.dmTelegramTitle = String(known[0].title || known[0].id);
11822
+ }
11823
+ }
11623
11824
  if (p.maxDailyLossPct !== undefined) f.maxDailyLossPct = Math.max(0, Math.min(50, Number(p.maxDailyLossPct) || 0));
11624
11825
  if (p.dailyLossAction !== undefined) {
11625
11826
  const v = String(p.dailyLossAction || 'warn').toLowerCase();
@@ -14024,8 +14225,6 @@ ipcMain.handle('tg:allow:set', (_e, list) => {
14024
14225
  saveSettings();
14025
14226
  return settings.tgAllow;
14026
14227
  });
14027
- /* Botun gördüğü gruplar (AJAN DM ↔ TELEGRAM grup seçicisi) */
14028
- ipcMain.handle('tg:groups:list', () => tgGroupsList());
14029
14228
  ipcMain.handle('tg:sessions', () => [...tgChats.values()]);
14030
14229
 
14031
14230
  /* Sohbet geçmişi etiketi (Telegram): "Telegram <ad|chatId>" */
package/src/preload.js CHANGED
@@ -221,7 +221,6 @@ 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'),
225
224
  tgListSessions: () => ipcRenderer.invoke('tg:sessions'),
226
225
  onTgEvent: (cb) => ipcRenderer.on('tg:event', (_e, ev) => cb(ev)),
227
226
  dcGetStatus: () => ipcRenderer.invoke('dc:status:get'),
@@ -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>
@@ -892,14 +892,14 @@
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 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.">
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ı — iki yönlü köprü</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>
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>
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
903
  <div id="finDmTgHint" class="fin-set-note" style="margin:2px 0 0"></div>
904
904
  </div>
905
905
  </div>
@@ -205,8 +205,8 @@ const els = {
205
205
  finShowFlow: $('#finShowFlow'),
206
206
  finDmTgOn: $('#finDmTgOn'),
207
207
  finDmTgWrap: $('#finDmTgWrap'),
208
- finDmTgChat: $('#finDmTgChat'),
209
- finDmTgRefresh: $('#finDmTgRefresh'),
208
+ finDmTgState: $('#finDmTgState'),
209
+ finDmTgUnbind: $('#finDmTgUnbind'),
210
210
  finDmTgHint: $('#finDmTgHint'),
211
211
  finRiskCard: $('#finRiskCard'),
212
212
  finNotifyCard: $('#finNotifyCard'),
@@ -10124,9 +10124,8 @@ async function finSnapshot() {
10124
10124
  if (!finView.risk && r.cfg && r.cfg.watchdog !== false) finSaveCfg({ watchdog: false });
10125
10125
  finRenderStats(r.stats, r.equity, r.account && r.account.currency ? r.account.currency : '');
10126
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) {
10127
+ /* görünüm ayarları açıkken AJAN DM ↔ TELEGRAM durumu cfg ile senkron kalsın */
10128
+ if (els.finSettingsOverlay && !els.finSettingsOverlay.hidden) {
10130
10129
  finDmTgPaint();
10131
10130
  }
10132
10131
  }
@@ -10291,10 +10290,9 @@ function finViewApply() {
10291
10290
  }
10292
10291
 
10293
10292
  /* ---------- 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
-
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. */
10298
10296
  function finDmTgPaint() {
10299
10297
  if (!els.finDmTgOn) return;
10300
10298
  const cfg = finCfgCache || {};
@@ -10303,50 +10301,24 @@ function finDmTgPaint() {
10303
10301
  const row = els.finDmTgOn.closest('.fin-set-row');
10304
10302
  if (row) row.classList.toggle('on', on);
10305
10303
  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
- }
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.';
10330
10309
  }
10310
+ if (els.finDmTgUnbind) els.finDmTgUnbind.hidden = !chat;
10331
10311
  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ı).';
10312
+ els.finDmTgHint.textContent =
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önetici (Konuları Yönet) olmalı — 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.';
10335
10316
  }
10336
10317
  }
10337
10318
 
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
-
10346
10319
  function finSettingsOpen() {
10347
10320
  if (els.finSettingsOverlay) els.finSettingsOverlay.hidden = false;
10348
10321
  finDmTgPaint();
10349
- finDmTgRefresh();
10350
10322
  }
10351
10323
  function finSettingsCloseModal() {
10352
10324
  if (els.finSettingsOverlay) els.finSettingsOverlay.hidden = true;
@@ -10380,30 +10352,27 @@ if (els.finShowRisk) els.finShowRisk.addEventListener('change', () => finViewTog
10380
10352
  if (els.finShowNotify) els.finShowNotify.addEventListener('change', () => finViewToggle('notify', els.finShowNotify.checked));
10381
10353
  if (els.finShowPerf) els.finShowPerf.addEventListener('change', () => finViewToggle('perf', els.finShowPerf.checked));
10382
10354
  if (els.finShowFlow) els.finShowFlow.addEventListener('change', () => finViewToggle('flow', els.finShowFlow.checked));
10383
- /* AJAN DM ↔ TELEGRAM köprüsü kontrolleri */
10355
+ /* AJAN DM ↔ TELEGRAM köprüsü kontrolleri (grup otomatik bağlanır) */
10384
10356
  if (els.finDmTgOn) {
10385
10357
  els.finDmTgOn.addEventListener('change', () => {
10386
10358
  const on = els.finDmTgOn.checked;
10387
10359
  if (finCfgCache) finCfgCache.dmTelegram = on;
10388
10360
  finSaveCfg({ dmTelegram: on });
10389
10361
  finDmTgPaint();
10390
- toast(on ? 'AJAN DM ↔ Telegram köprüsü açıldı' : 'AJAN DM ↔ Telegram köprüsü kapatıldı');
10391
- if (on) finDmTgRefresh();
10362
+ toast(on ? 'AJAN DM ↔ Telegram açıldı — gruba /start yazınca otomatik bağlanır' : 'AJAN DM ↔ Telegram kapatıldı');
10392
10363
  });
10393
10364
  }
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));
10365
+ if (els.finDmTgUnbind) {
10366
+ els.finDmTgUnbind.addEventListener('click', () => {
10398
10367
  if (finCfgCache) {
10399
- finCfgCache.dmTelegramChat = id;
10400
- finCfgCache.dmTelegramTitle = g ? String(g.title || '') : '';
10368
+ finCfgCache.dmTelegramChat = '';
10369
+ finCfgCache.dmTelegramTitle = '';
10401
10370
  }
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');
10371
+ finSaveCfg({ dmTelegramChat: '', dmTelegramTitle: '' });
10372
+ finDmTgPaint();
10373
+ toast('Telegram grubu koparıldı — yeni grupta /start yazınca bağlanır');
10404
10374
  });
10405
10375
  }
10406
- if (els.finDmTgRefresh) els.finDmTgRefresh.addEventListener('click', () => finDmTgRefresh());
10407
10376
  finViewApply();
10408
10377
 
10409
10378
  /* ---------- BÖLÜM SIRALAMA (sürükle-bırak) ----------
@@ -5882,7 +5882,7 @@ body.finance-mode #composerWrap { margin-right: var(--finW, 400px); }
5882
5882
  }
5883
5883
  .fin-set-sub[hidden] { display: none; }
5884
5884
  .fin-set-lbl { font-size: 10.5px; color: var(--muted); letter-spacing: 0.5px; }
5885
- .fin-set-sub select { width: 100%; }
5885
+ .fin-dm-tg-state { font-size: 11.5px; color: var(--text); line-height: 1.4; word-break: break-word; }
5886
5886
  .fin-set-btn {
5887
5887
  align-self: flex-start;
5888
5888
  font-size: 10.5px;