beast-agent 2.57.1 → 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.1",
4
+ "version": "2.58.0",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
@@ -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;
package/src/main.js CHANGED
@@ -2956,7 +2956,9 @@ async function handleTgIncoming(chatId, payload) {
2956
2956
  return;
2957
2957
  }
2958
2958
  if (String(dmTg.chat) === String(chatId)) {
2959
- finTelegramToAgentDm(payload);
2959
+ /* FORUM: konu (thread) → ilgili AJAN DM grubu; konusuz/genel → finance */
2960
+ const gid = finDmTgGidForThread(payload.messageThreadId);
2961
+ finTelegramToAgentDm(payload, gid);
2960
2962
  return;
2961
2963
  }
2962
2964
  tgLog(`skip: grup mesajı chat=${chatId} (bağlı grup ${dmTg.chat})`);
@@ -9416,12 +9418,14 @@ function finCfg() {
9416
9418
  iptal) düşer; fiyat alarmı, koruma (BE/trailing/kısmi TP), günlük limit ve
9417
9419
  rapor bildirimleri panelde kalır. false = tüm olaylar kanallara gider. */
9418
9420
  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. */
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. */
9422
9425
  if (typeof f.dmTelegram !== 'boolean') f.dmTelegram = false;
9423
9426
  if (typeof f.dmTelegramChat !== 'string') f.dmTelegramChat = '';
9424
9427
  if (typeof f.dmTelegramTitle !== 'string') f.dmTelegramTitle = '';
9428
+ if (!f.dmTelegramTopics || typeof f.dmTelegramTopics !== 'object') f.dmTelegramTopics = {};
9425
9429
  if (!Number.isFinite(Number(f.maxDailyLossPct))) f.maxDailyLossPct = 3;
9426
9430
  /* GÜNLÜK LİMİT AKSİYONU: warn = yalnız uyarı; stop = tüm finance ajanlarını
9427
9431
  durdur; flatten = durdur + tüm pozisyonları kapat */
@@ -10100,6 +10104,12 @@ function finDmTgBind(chatId, title) {
10100
10104
  if (!id) return false;
10101
10105
  const f = finCfg();
10102
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
+ }
10103
10113
  f.dmTelegramChat = id;
10104
10114
  f.dmTelegramTitle = t || f.dmTelegramTitle || id;
10105
10115
  try { saveSettings(); } catch {}
@@ -10111,7 +10121,8 @@ function finDmTgBind(chatId, title) {
10111
10121
  tg.send(
10112
10122
  id,
10113
10123
  '✅ 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.'
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.'
10115
10126
  )
10116
10127
  ).catch(() => {});
10117
10128
  }
@@ -10119,36 +10130,152 @@ function finDmTgBind(chatId, title) {
10119
10130
  return true;
10120
10131
  }
10121
10132
 
10122
- /* Giden: AJAN DM grubu → Telegram (Telegram'dan gelenler geri gönderilmez;
10123
- grup henüz otomatik bağlanmadıysa sessizce beklenir). */
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). */
10124
10231
  function finAgentDmToTelegram(dm) {
10125
10232
  try {
10126
10233
  if (!dm || dm.viaTelegram) return;
10127
- 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) */
10128
10236
  const c = finDmTgCfg();
10129
10237
  if (!c.on || !c.chat || !tg || !tg.connected) return;
10238
+ const title = String(dm.groupTitle || (gid === FIN_TEAM_GID ? FIN_TEAM_TITLE : gid));
10130
10239
  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
- );
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
+ });
10137
10254
  } catch {}
10138
10255
  }
10139
10256
 
10140
- /* Gelen: Telegram grubu → AJAN DM grubu (+ tüm koşan finance ajanlarını uyandır). */
10141
- 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) {
10142
10260
  try {
10143
10261
  const body = String((payload && payload.text) || '').trim();
10144
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
+ );
10145
10272
  const who = String((payload && (payload.senderName || payload.username)) || 'Telegram');
10146
10273
  const label = `Sahip · Telegram (${who})`;
10147
10274
  try {
10148
10275
  if (typeof engine.agentDmGroupPost === 'function') {
10149
10276
  engine.agentDmGroupPost({
10150
- gid: FIN_TEAM_GID,
10151
- title: FIN_TEAM_TITLE,
10277
+ gid: key,
10278
+ title,
10152
10279
  fromSid: 'tg:' + String((payload && payload.senderId) || ''),
10153
10280
  fromTitle: label,
10154
10281
  topic: 'telegram',
@@ -10157,14 +10284,31 @@ function finTelegramToAgentDm(payload) {
10157
10284
  });
10158
10285
  }
10159
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) : [];
10160
10290
  const wake =
10161
- `[AJAN DM — TELEGRAM GRUBU · ${label}]\n${body}\n` +
10291
+ `[AJAN DM — TELEGRAM${title ? ' · ' + title : ''} · ${label}]\n${body}\n` +
10162
10292
  'Şimdi yap: sahibin Telegram grubundan gelen bu mesajı değerlendir; gerekiyorsa ekipçe aksiyon al. Kısa rapor ver.';
10163
10293
  let sent = 0;
10164
10294
  for (const [sid] of financeState.agents) {
10295
+ if (key !== FIN_TEAM_GID && members.length && !members.includes(String(sid))) continue;
10165
10296
  if (finWakeAgentDm(sid, wake)) sent += 1;
10166
10297
  }
10167
- 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ı)`);
10168
10312
  return { ok: true, sent };
10169
10313
  } catch {
10170
10314
  return { ok: false };
@@ -11658,8 +11802,15 @@ ipcMain.handle('finance:settings', async (_e, patch) => {
11658
11802
  /* AJAN DM ↔ TELEGRAM köprüsü ayarları (grup SEÇİLMEZ — otomatik bağlanır) */
11659
11803
  if (p.dmTelegram !== undefined) f.dmTelegram = !!p.dmTelegram;
11660
11804
  if (p.dmTelegramChat !== undefined) {
11805
+ const prevChat = String(f.dmTelegramChat || '');
11661
11806
  f.dmTelegramChat = String(p.dmTelegramChat || '').trim().slice(0, 64);
11662
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
+ }
11663
11814
  }
11664
11815
  if (p.dmTelegramTitle !== undefined) f.dmTelegramTitle = String(p.dmTelegramTitle || '').trim().slice(0, 80);
11665
11816
  /* 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ö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.';
10314
10316
  }
10315
10317
  }
10316
10318