beast-agent 2.62.1 → 2.63.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 +1 -1
- package/src/agent/defaulttools/mt5_shot/run.js +31 -23
- package/src/agent/defaulttools/mt5_shot/tool.json +1 -1
- package/src/agent/engine.js +163 -64
- package/src/agent/financetools.js +103 -1
- package/src/agent/finwatch.js +19 -1
- package/src/agent/mt5/BeastFinance.mq5 +80 -23
- package/src/agent/seeds/price-action/SKILL.md +2 -1
- package/src/agent/seeds/risk-yonetimi/SKILL.md +2 -0
- package/src/main.js +378 -5
- package/src/renderer/index.html +3 -1
- package/src/renderer/renderer.js +48 -11
package/package.json
CHANGED
|
@@ -20,8 +20,10 @@
|
|
|
20
20
|
Notlar:
|
|
21
21
|
- beast_cmd.json'a {id, ts, cmd, params} yazılır (UTF-16 BOM; EA FILE_UNICODE okur).
|
|
22
22
|
- Ack dosyası POLL edilir; PNG MQL5\Files altında belirene kadar beklenir.
|
|
23
|
-
- EA v1.
|
|
23
|
+
- EA v1.22 şu şablonu işler: {"id":"..","ts":..,"cmd":"shot","params":{file,width,height,symbol?,timeframe?,template?}}
|
|
24
24
|
symbol/timeframe aktif grafikten farklıysa EA ChartOpen+ChartSetSymbolPeriod ile geçici grafik açar.
|
|
25
|
+
EA ack'i PNG üretimini BEKLEMEDEN yazar (ChartScreenShot asenkron; geçici grafik kapatma
|
|
26
|
+
bir sonraki timer turuna ertelenir) — ack = "komut işlendi", PNG ayrıca beklenir.
|
|
25
27
|
- PNG, send_file'ın engellediği %APPDATA%\beast altından çıkarılıp kullanıcı klasörüne
|
|
26
28
|
(varsayılan <home>\beast_shots) kopyalanır; dönen "path" doğrudan send_file'a verilebilir.
|
|
27
29
|
- GÖRSEL ENJEKSİYONU: başarılı sonuçta PNG data URL olarak __injectImage alanına gömülür;
|
|
@@ -39,10 +41,12 @@ const LOCK_STALE_MS = 45000;
|
|
|
39
41
|
const HARD_MS = 80000;
|
|
40
42
|
const DEFAULT_TIMEOUT_SEC = 20;
|
|
41
43
|
|
|
42
|
-
/* ---------------- temel yardımcılar ----------------
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
/* ---------------- temel yardımcılar ----------------
|
|
45
|
+
ÖNEMLİ: beklemeler ASENKRON olmalı — Atomics.wait tabanlı senkron uyku ana
|
|
46
|
+
thread'i kilitler, watchdog ZAMANLAYICISI ateşlenemez ve araç 80 sn yerine
|
|
47
|
+
customtools'un 90 sn kill'ine takılırdı ("tool zaman aşımı" + ack kaybı). */
|
|
48
|
+
function sleep(ms) {
|
|
49
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
let CTX = { cmdId: null, filesDir: null, started: Date.now() };
|
|
@@ -74,7 +78,7 @@ function fail(code, msg, extra) {
|
|
|
74
78
|
/* ---------------- kilit (eşzamanlı çağrılar ack'i ezmesin) ---------------- */
|
|
75
79
|
function releaseLock() { try { fs.unlinkSync(LOCK_FILE); } catch (e) {} }
|
|
76
80
|
|
|
77
|
-
function acquireLock(waitMs) {
|
|
81
|
+
async function acquireLock(waitMs) {
|
|
78
82
|
const deadline = Date.now() + waitMs;
|
|
79
83
|
for (;;) {
|
|
80
84
|
try { fs.writeFileSync(LOCK_FILE, JSON.stringify({ pid: process.pid, at: Date.now() }), { flag: 'wx' }); return true; }
|
|
@@ -83,7 +87,7 @@ function acquireLock(waitMs) {
|
|
|
83
87
|
try { st = fs.statSync(LOCK_FILE); } catch (x) {
|
|
84
88
|
// kilit kayboldu/kilitli kaldı: kısa bekle, sonsuz döngüye girme
|
|
85
89
|
if (Date.now() >= deadline) return false;
|
|
86
|
-
|
|
90
|
+
await sleep(200);
|
|
87
91
|
continue;
|
|
88
92
|
}
|
|
89
93
|
if (Date.now() - st.mtimeMs > LOCK_STALE_MS) { // ölü kilit: devral
|
|
@@ -91,7 +95,7 @@ function acquireLock(waitMs) {
|
|
|
91
95
|
continue;
|
|
92
96
|
}
|
|
93
97
|
if (Date.now() >= deadline) return false;
|
|
94
|
-
|
|
98
|
+
await sleep(400);
|
|
95
99
|
}
|
|
96
100
|
}
|
|
97
101
|
}
|
|
@@ -154,7 +158,7 @@ function tfName(p) {
|
|
|
154
158
|
}
|
|
155
159
|
|
|
156
160
|
/* ack yoksa: kanal sağlam mı? kısa bir ping ile ayırt et */
|
|
157
|
-
function eaPing(filesDir, budgetMs) {
|
|
161
|
+
async function eaPing(filesDir, budgetMs) {
|
|
158
162
|
const id = 'P' + Date.now().toString(36);
|
|
159
163
|
const cmdFile = path.join(filesDir, 'beast_cmd.json');
|
|
160
164
|
const ackFile = path.join(filesDir, 'beast_cmd_ack.json');
|
|
@@ -163,7 +167,7 @@ function eaPing(filesDir, budgetMs) {
|
|
|
163
167
|
catch (e) { return { ok: false, error: 'ping yazılamadı: ' + e.message }; }
|
|
164
168
|
const dl = Date.now() + budgetMs;
|
|
165
169
|
while (Date.now() < dl) {
|
|
166
|
-
|
|
170
|
+
await sleep(300);
|
|
167
171
|
const a = readJson(ackFile);
|
|
168
172
|
if (a && a.id === id) return { ok: true, ack: a, ms: Date.now() - t0 };
|
|
169
173
|
}
|
|
@@ -231,7 +235,7 @@ function sanitizeFile(name, fallback) {
|
|
|
231
235
|
}
|
|
232
236
|
|
|
233
237
|
/* tek kare: EA'ya shot komutu yaz → ack bekle → PNG'yi bekle */
|
|
234
|
-
function captureOne(o) {
|
|
238
|
+
async function captureOne(o) {
|
|
235
239
|
CTX.cmdId = o.id;
|
|
236
240
|
const payload = { id: o.id, ts: Date.now(), cmd: 'shot', params: o.params };
|
|
237
241
|
try { if (fs.existsSync(o.ackFile)) fs.unlinkSync(o.ackFile); } catch (e) {}
|
|
@@ -242,7 +246,7 @@ function captureOne(o) {
|
|
|
242
246
|
let ack = null, cmdConsumed = false;
|
|
243
247
|
const ackDeadline = Date.now() + o.timeoutSec * 1000;
|
|
244
248
|
while (Date.now() < ackDeadline) {
|
|
245
|
-
|
|
249
|
+
await sleep(250);
|
|
246
250
|
const a = readJson(o.ackFile);
|
|
247
251
|
if (a && a.id === o.id) { ack = a; break; }
|
|
248
252
|
try {
|
|
@@ -265,7 +269,7 @@ function captureOne(o) {
|
|
|
265
269
|
extra.diagnostic = cmdConsumed
|
|
266
270
|
? 'EA komutu aldı ama ack yazmadı (shot işleyicisi hata verdi / PNG üretimi bloke)'
|
|
267
271
|
: 'EA komutu hiç almadı (zamanlayıcı durmuş ya da komut dosyası okunamadı)';
|
|
268
|
-
extra.ping = eaPing(o.filesDir, 6000);
|
|
272
|
+
extra.ping = await eaPing(o.filesDir, 6000);
|
|
269
273
|
}
|
|
270
274
|
return { ok: false, err: 'ea_no_ack', error: 'EA ' + o.timeoutSec + ' sn içinde ack vermedi (id=' + o.id + ', heartbeat ' +
|
|
271
275
|
(extra.heartbeat_age_sec === null ? '?' : extra.heartbeat_age_sec) + ' sn, cmd tüketildi=' + cmdConsumed + ')', extra };
|
|
@@ -279,11 +283,11 @@ function captureOne(o) {
|
|
|
279
283
|
const shotFile = (ack.result && ack.result.file) ? path.basename(String(ack.result.file)) : o.params.file;
|
|
280
284
|
const src = path.join(o.filesDir, shotFile);
|
|
281
285
|
let size = 0;
|
|
282
|
-
const pngDeadline = Math.max(ackDeadline, Date.now() +
|
|
286
|
+
const pngDeadline = Math.max(ackDeadline, Date.now() + 10000);
|
|
283
287
|
for (;;) {
|
|
284
288
|
try { if (fs.existsSync(src)) { size = fs.statSync(src).size; if (size > 1500) break; } } catch (e) {}
|
|
285
289
|
if (Date.now() >= pngDeadline) break;
|
|
286
|
-
|
|
290
|
+
await sleep(250);
|
|
287
291
|
}
|
|
288
292
|
if (size <= 1500) {
|
|
289
293
|
return { ok: false, err: 'png_yok', error: 'PNG oluşmadı ya da çok küçük (' + size + ' byte): ' + shotFile, extra: { ack, expected_file: shotFile, bytes: size } };
|
|
@@ -353,7 +357,7 @@ async function runPanels(plan, filesDir, cmdFile, ackFile, beatFile, timeoutSec,
|
|
|
353
357
|
if (symArg) params.symbol = symArg;
|
|
354
358
|
if (ARGS.template !== undefined) params.template = String(ARGS.template);
|
|
355
359
|
cmdIds.push(id);
|
|
356
|
-
const r = captureOne({ id, file: params.file, params, filesDir, cmdFile, ackFile, beatFile, timeoutSec: perAck, diag });
|
|
360
|
+
const r = await captureOne({ id, file: params.file, params, filesDir, cmdFile, ackFile, beatFile, timeoutSec: perAck, diag });
|
|
357
361
|
if (!r.ok) {
|
|
358
362
|
return {
|
|
359
363
|
err: r.err,
|
|
@@ -452,7 +456,7 @@ async function run() {
|
|
|
452
456
|
{ heartbeat_age_sec: beatAge });
|
|
453
457
|
}
|
|
454
458
|
|
|
455
|
-
const locked = acquireLock(Math.min(15000, timeoutSec * 500));
|
|
459
|
+
const locked = await acquireLock(Math.min(15000, timeoutSec * 500));
|
|
456
460
|
if (!locked) console.error('[mt5_shot] uyarı: kilit alınamadı, yine de devam ediliyor');
|
|
457
461
|
|
|
458
462
|
/* ÇOKLU KARE: symbols:["GOLD","EURUSD"] ve/veya timeframes:["M1","M15"]
|
|
@@ -476,7 +480,7 @@ async function run() {
|
|
|
476
480
|
params.width = Number(ARGS.width) > 0 ? Math.min(10000, Math.round(Number(ARGS.width))) : 1600;
|
|
477
481
|
params.height = Number(ARGS.height) > 0 ? Math.min(10000, Math.round(Number(ARGS.height))) : 900;
|
|
478
482
|
|
|
479
|
-
const cap = captureOne({ id, file, params, filesDir, cmdFile, ackFile, beatFile, timeoutSec, diag });
|
|
483
|
+
const cap = await captureOne({ id, file, params, filesDir, cmdFile, ackFile, beatFile, timeoutSec, diag });
|
|
480
484
|
if (!cap.ok) return fail(cap.err, cap.error, cap.extra);
|
|
481
485
|
|
|
482
486
|
const cp = copyOut(cap.src, cap.shotFile, ARGS.outdir);
|
|
@@ -576,11 +580,15 @@ function loadArgs(rawStdin) {
|
|
|
576
580
|
const st = fs.fstatSync(0);
|
|
577
581
|
f0 = { isFIFO: st.isFIFO(), isCharacterDevice: st.isCharacterDevice(), isFile: st.isFile(), size: st.size };
|
|
578
582
|
} catch (e) { f0 = { error: String(e && e.message) }; }
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
583
|
+
/* Parent komut satırı teşhisi pahalıdır (PowerShell ~1-5 sn) — yalnız
|
|
584
|
+
BEAST_TOOL_DIAG=1 iken koşar; normal çekimlerde sn cinsinden hız kazandırır. */
|
|
585
|
+
if (process.env.BEAST_TOOL_DIAG === '1') {
|
|
586
|
+
try {
|
|
587
|
+
const { execSync } = require('child_process');
|
|
588
|
+
pcmd = execSync('powershell -NoProfile -Command "(Get-CimInstance Win32_Process -Filter \\"ProcessId=' +
|
|
589
|
+
process.ppid + '\\").CommandLine"', { timeout: 5000 }).toString().trim();
|
|
590
|
+
} catch (e) { pcmd = 'okunamadı: ' + String(e && e.message).slice(0, 120); }
|
|
591
|
+
}
|
|
584
592
|
const envJson = {};
|
|
585
593
|
for (const k of Object.keys(process.env)) {
|
|
586
594
|
if (/key|token|secret|pass|auth|credential/i.test(k)) continue;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mt5_shot",
|
|
3
|
-
"defaultsVersion":
|
|
3
|
+
"defaultsVersion": 6,
|
|
4
4
|
"description": "MT5 grafiğinin GERÇEK ekran görüntüsünü alır ve GÖRSELİ ÇAĞIRAN AJANA GÖSTERİR: MT5\\MQL5\\Files\\beast_cmd.json üzerinden BeastFinance EA'ya 'shot' komutu yazar, EA ChartScreenShot ile PNG üretir; PNG hem tool klasörüne hem <kullanıcı>\\beast_shots altına kopyalanır (dönen path send_file'a verilebilir) hem de data URL olarak sonuca gömülür — engine bunu sonraki turda ajana görsel olarak enjekte eder (embed:false kapatır). ÇOKLU SEMBOL: symbols:[\"GOLD\",\"EURUSD\",\"BTCUSD\"] (2-4 sembol) verirsen her sembolden bir kare çekilip TEK PNG'de birleştirilir (izleme listesinin tamamı TEK görselle gönderilir); timeframes ile birlikte verilirse sembol × periyot ızgarası kurulur (en fazla 6 kare). ÇOKLU ZAMAN DİLİMİ: timeframes:[\"M1\",\"M15\"] verirsen aynı sembolün periyotları SIRAYLA çekilip TEK PNG'de birleştirilir (layout:\"h\" soldan sağa — varsayılan; \"v\" üstten alta; 2-3 periyot) ve her panelin SAĞ ÜST köşesine BÜYÜK periyot etiketi (üstünde küçük sembol) çizilir — solda/sağda hangi sembol/periyot olduğu görüntüden bakışta okunur. Aktif grafik istenen sembol/periyot ile aynıysa ondan çekilir (Beast panosu + seviye çizgileri görünür), değilse EA geçici grafik açar (ChartOpen/ChartSetSymbolPeriod + BeastShot.tpl açık tema); çoklu sembolde her sembol için sırayla. Çekim öncesi grafiğe sağ boşluk payı (chart shift %12) uygulanır — SON MUM sağ kenara yapışmaz; aktif grafikte önceki ayar çekimden sonra geri yazılır. Komut id/ts ile yazılır, ack poll edilir ve PNG MQL5\\Files'ta belirene kadar beklenir; hata halinde gövde şu kodu taşır: err = no_terminal|ea_offline|cmd_write_fail|ea_no_ack|ea_error|png_yok|timeout|stitch_fail (+ cmdId, filesDir, diagnostic, ping). Grafik analizi/raporu ve ekip paylaşımı için birincil screenshot aracıdır.",
|
|
5
5
|
"parameters": {
|
|
6
6
|
"type": "object",
|
package/src/agent/engine.js
CHANGED
|
@@ -2032,7 +2032,7 @@ class Engine {
|
|
|
2032
2032
|
modeBlock2 + '\n' +
|
|
2033
2033
|
(roleBlockFull ? roleBlockFull + '\n' : '') +
|
|
2034
2034
|
(teamLine || '') +
|
|
2035
|
-
'MT5 ARAÇLARI: mt5_status (bağlantı), mt5_account (hesap), mt5_market (canlı fiyat), mt5_positions (açık pozisyonlar), mt5_orders (bekleyen emirler), mt5_history (kapanan işlemler), mt5_alerts (fiyat alarmı: kur/listele/sil — modu SEN seçersin: once/repeat), mt5_ea (BeastFinance grafik panosu: status/ping/chart/note), mt5_trade (⚡PİYASA EMRİ: side:"buy"|"sell" ile ANLIK gir — bekleyen emir ZORUNLU DEĞİL; lot için volume yerine riskPct+sl ver, sistem hesaplar; limit/stop tipleri de kabul eder), mt5_close (TAM ya da KISMİ kapat: percent ile kısmi TP & kısmi stop — karar ajanın), mt5_modify (SL/TP), mt5_pending (BEKLEYEN emir: limit/stop + price — fiyatın seviyeye gelmesini beklemek için; anlık giriş için mt5_trade kullan), mt5_cancel (emir iptal).\n' +
|
|
2035
|
+
'MT5 ARAÇLARI: mt5_status (bağlantı), mt5_account (hesap), mt5_market (canlı fiyat), mt5_positions (açık pozisyonlar), mt5_orders (bekleyen emirler), mt5_history (kapanan işlemler), mt5_alerts (fiyat alarmı: kur/listele/sil — modu SEN seçersin: once/repeat), mt5_ogrenme (SEMBOL BAZLI ÖĞRENME: işlem istatistiği + dersler — her kapanıştan sonra ders yaz, karar öncesi oku), mt5_limits (lot/pozisyon limitlerini gör/GÜNCELLE — yalnız trader), mt5_ea (BeastFinance grafik panosu: status/ping/chart/note), mt5_trade (⚡PİYASA EMRİ: side:"buy"|"sell" ile ANLIK gir — bekleyen emir ZORUNLU DEĞİL; lot için volume yerine riskPct+sl ver, sistem hesaplar; limit/stop tipleri de kabul eder), mt5_close (TAM ya da KISMİ kapat: percent ile kısmi TP & kısmi stop — karar ajanın), mt5_modify (SL/TP), mt5_pending (BEKLEYEN emir: limit/stop + price — fiyatın seviyeye gelmesini beklemek için; anlık giriş için mt5_trade kullan), mt5_cancel (emir iptal).\n' +
|
|
2036
2036
|
'EMİR TİPLERİ (6 tip — HEPSİ AÇIK): buy_market/sell_market = anlık piyasa emri (fiyat beklenmez), buy_limit/sell_limit = bekleyen limit (price zorunlu), buy_stop/sell_stop = bekleyen stop (price zorunlu). mt5_trade ve mt5_pending İKİSİ DE 6 tipi kabul eder; emir tipini net söyle (ör. type:"sell_limit").\n' +
|
|
2037
2037
|
'⚡HIZLI AKSİYON (ZORUNLU ZİHNİYET): piyasa emri (market buy/sell) HER ZAMAN elinde ve bekleyen emir vermek ZORUNDA DEĞİLSİN. Fırsat anıksa / teyitli kırılım, momentum, haber veya seviye reddi anında mt5_trade {symbol, side:"buy"|"sell", sl, tp, riskPct ya da volume, reason} ile ANINDA market gir — type bile yazmana gerek yok. Bekleyen emir YALNIZ fiyatın seviyeye gelmesini beklemek gerçekten mantıklıysa kurulur (ör. geri çekilme limiti); "emir" diye her şeyi bekleyen emirle yapma. Hızlı girişte tek çağrı yeter: volume yerine riskPct+sl ver → lot otomatik hesaplanır.\n' +
|
|
2038
2038
|
'ALARM KARARI SENDE: mt5_alerts ile alarm kurarken mode ZORUNLU — "once" (tek seferlik; ilk tetiklemede kapanır) ya da "repeat" (tekrarlı; alarm açık kalır, koşul sürdükçe cooldownMin dakikada bir tekrar uyarır + seni uyandırır; cooldownMin varsayılan 5). Tekrarlı mı tek seferlik mi olacağını sen seç, varsayılana bırakma; seviyenin önemine göre cooldownMin belirle (ör. 5/15/60).\n' +
|
|
@@ -2068,9 +2068,13 @@ class Engine {
|
|
|
2068
2068
|
: '') +
|
|
2069
2069
|
'RİSK DİSİPLİNİ:\n' +
|
|
2070
2070
|
'- SL\u2019siz pozisyon BIRAKMA: açtığın her işlemde mantıklı bir SL koy; SL\u2019siz kalan pozisyonu mt5_modify ile tamamla.\n' +
|
|
2071
|
+
'- KÂR KORUMA (SİSTEM OTOMATİK): kâr 0.3R\'ye ulaşınca SL, görülen en iyi kârın 0.15R gerisine kilitlenir — kâr eksiye dönmez. Bu yüzden kârı erken koru; hedefe ulaşmayan işlemde bile kârı geri verme.\n' +
|
|
2072
|
+
'- TP İÇİN RR ≥ 1.5 ŞARTI YOK: scalp/hızlı işlemlerde TP 0.3R-0.5R olabilir. Kısmi kâr alımı SERBEST ve TEŞVİK EDİLİR: 0.3-0.5R\'de pozisyonun bir kısmını mt5_close {percent, kind:"partial_tp"} ile realize et, kalanı korumalı trailing ile taşı. Amaç: sık işlem + hızlı kâr toplama; kârı masada bekletme.\n' +
|
|
2071
2073
|
'- Martingale/kademeli lot artışı YASAK; kaybı geri kovalama (revenge trade) YASAK.\n' +
|
|
2072
|
-
'- Emin olmadığında İŞLEM YOK — "BEKLE: <sebep>" yaz. Sık işlem
|
|
2073
|
-
'- Hesap kaldıracı ve serbest marja göre pozisyon boyutunu düşük tut; tek işlemde serbest marjın büyük kısmını riske atma.\n' +
|
|
2074
|
+
'- Emin olmadığında İŞLEM YOK — "BEKLE: <sebep>" yaz. Sık işlem tek başına iyi işlem değil; ama onaylı fırsatta HIZLI gir, kârı hızlı topla.\n' +
|
|
2075
|
+
'- Hesap kaldıracı ve serbest marja göre pozisyon boyutunu düşük tut; tek işlemde serbest marjın büyük kısmını riske atma. Sık işlem için lotu değil, TUR SIKLIĞINI ve kısmi kâr alımını kullan.\n' +
|
|
2076
|
+
(session && session.financeLearn ? `SEMBOL BAZLI ÖĞRENME HAFIZAN (otomatik istatistik + kendi derslerin — kararlarında kullan):\n${session.financeLearn}\n` : '') +
|
|
2077
|
+
'ÖĞRENME DÖNGÜSÜ (ZORUNLU): her kapanıştan (özellikle stop/TP sonrası) hemen sonra mt5_ogrenme {action:"add", symbol, text, kind} ile o sembolün dersini yaz (ne işe yaradı/yaramadı, hata mı, kural mı); yeni işlem kararından önce mt5_ogrenme {action:"stats", symbol} ya da list ile o sembolün geçmişini oku — aynı hatayı tekrarlama, işleyen deseni kullan. Geçersiz dersi remove/clear ile sil.\n' +
|
|
2074
2078
|
(session && session.financeStrategy ? `SAHİBİNİN STRATEJİ NOTU (önceliklidir):\n${session.financeStrategy}\n` : '') +
|
|
2075
2079
|
(session && session.financeShadow ? 'SHADOW MOD AKTİF: mt5_trade/mt5_pending emir GÖNDERMEZ — kararını teziyle raporla; gerçek işlem açılmaz (karar günlüğe yazılır).\n' : '') +
|
|
2076
2080
|
(session && session.financeDigest ? `İŞLEM PERFORMANS GEÇMİŞİN (kendi kayıtların — kararlarında ders çıkar):\n${session.financeDigest}\n` : '') +
|
|
@@ -2858,37 +2862,45 @@ class Engine {
|
|
|
2858
2862
|
}
|
|
2859
2863
|
}
|
|
2860
2864
|
|
|
2861
|
-
/* SÜREKLİ ajan DM INBOX (bkz. flushPendingReports
|
|
2862
|
-
DM'ler yeni tur AÇMAZ; sıradaki
|
|
2863
|
-
eklenir — ajan-ajan sonsuz DM ping-pong'u
|
|
2865
|
+
/* SÜREKLİ ajan DM INBOX + BOT DM SEPETİ (bkz. flushPendingReports ve
|
|
2866
|
+
_agentDmDeliver): tur aralığında biriken DM'ler yeni tur AÇMAZ; sıradaki
|
|
2867
|
+
mesajın önüne tek blok olarak eklenir — ajan-ajan sonsuz DM ping-pong'u,
|
|
2868
|
+
tur fırtınası ve bağlam şişmesi engellenir. */
|
|
2864
2869
|
try {
|
|
2870
|
+
const pendingDm = [];
|
|
2865
2871
|
const jobRec = this._bgJobs && this._bgJobs.get(String(sessionId));
|
|
2866
2872
|
if (jobRec && Array.isArray(jobRec.dmInbox) && jobRec.dmInbox.length) {
|
|
2867
|
-
const
|
|
2868
|
-
|
|
2873
|
+
for (const x of jobRec.dmInbox.splice(0)) pendingDm.push(x);
|
|
2874
|
+
}
|
|
2875
|
+
if (this._agentDmBacklog && this._agentDmBacklog.size) {
|
|
2876
|
+
const q = this._agentDmBacklog.get(String(sessionId));
|
|
2877
|
+
if (q && q.length) for (const x of q.splice(0)) pendingDm.push(x);
|
|
2878
|
+
}
|
|
2879
|
+
if (pendingDm.length) {
|
|
2880
|
+
const boxText = pendingDm
|
|
2869
2881
|
.map((x) => (typeof x === 'string' ? x : String((x && x.text) || '')))
|
|
2870
2882
|
.join('\n\n')
|
|
2871
2883
|
.slice(0, 6000);
|
|
2872
2884
|
/* görselli DM'ler: son 2 görsel bu turun mesajına vision olarak eklenir */
|
|
2873
|
-
const
|
|
2885
|
+
const dmImgs = pendingDm
|
|
2874
2886
|
.map((x) => (x && typeof x === 'object' && /^data:image\//i.test(String(x.image || '')) ? String(x.image) : ''))
|
|
2875
2887
|
.filter(Boolean)
|
|
2876
2888
|
.slice(-2);
|
|
2877
2889
|
const prefix =
|
|
2878
|
-
`[BEKLEYEN AJAN DM'LERİ — ${
|
|
2890
|
+
`[BEKLEYEN AJAN DM'LERİ — ${pendingDm.length} mesaj]\n${boxText}\n` +
|
|
2879
2891
|
`(Tur aralığında biriken DM'ler. Bu turda değerlendir; yalnızca aksiyon/cevap GEREKİYORSA agent_dm ile TEK kısa cevap ver — teşekkür/onay/ack mesajı YAZMA.)\n\n`;
|
|
2880
2892
|
if (typeof msg.content === 'string') {
|
|
2881
|
-
msg.content =
|
|
2893
|
+
msg.content = dmImgs.length
|
|
2882
2894
|
? [
|
|
2883
2895
|
{ type: 'text', text: prefix + msg.content },
|
|
2884
|
-
...
|
|
2896
|
+
...dmImgs.map((u) => ({ type: 'image_url', image_url: { url: u } })),
|
|
2885
2897
|
]
|
|
2886
2898
|
: prefix + msg.content;
|
|
2887
2899
|
} else if (Array.isArray(msg.content)) {
|
|
2888
2900
|
if (msg.content[0] && typeof msg.content[0].text === 'string') {
|
|
2889
2901
|
msg.content[0] = { ...msg.content[0], text: prefix + msg.content[0].text };
|
|
2890
2902
|
}
|
|
2891
|
-
for (const u of
|
|
2903
|
+
for (const u of dmImgs) msg.content.push({ type: 'image_url', image_url: { url: u } });
|
|
2892
2904
|
}
|
|
2893
2905
|
}
|
|
2894
2906
|
} catch {}
|
|
@@ -3843,14 +3855,11 @@ class Engine {
|
|
|
3843
3855
|
);
|
|
3844
3856
|
if (skillDef) toolsList = [...toolsList, skillDef];
|
|
3845
3857
|
}
|
|
3846
|
-
/* AJAN DM: koşan
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
!session.isBotDm &&
|
|
3852
|
-
(session.bgJob || session.finance || !session.botId || session.botId === 'beast')
|
|
3853
|
-
) {
|
|
3858
|
+
/* AJAN DM: koşan ajanlara (arka plan işleri + finance), ana sohbete VE
|
|
3859
|
+
bot sohbetlerine verilir — botlar birbirine iş atar/cevap verir
|
|
3860
|
+
(ör. Beast → Tool botu; Tool botu da sonucu DM ile geri yazar).
|
|
3861
|
+
Yalnız botlar arası DM pair oturumunda kapalı (döngü koruması). */
|
|
3862
|
+
if (session && !session.isBotDm) {
|
|
3854
3863
|
toolsList = [...toolsList, AGENT_DM_DEF];
|
|
3855
3864
|
}
|
|
3856
3865
|
/* TOOL İSTEĞİ (cephane): tool botu hariç TÜM oturumlar — müşteri bot
|
|
@@ -3920,6 +3929,15 @@ class Engine {
|
|
|
3920
3929
|
const allow = new Set(adef.tools);
|
|
3921
3930
|
activeTools = activeTools.filter((t) => allow.has(t.function.name));
|
|
3922
3931
|
}
|
|
3932
|
+
/* KİŞİSEL/VARSAYILAN TOOLLAR (tool__*) CEPHANE KURALI: bot skill
|
|
3933
|
+
whitelist'i (toolLimit) ve ajan beyaz listesi bunları düşürmesin —
|
|
3934
|
+
Tool botu onardığı aracı kendi oturumunda test edebilsin, diğer
|
|
3935
|
+
botlar da kullanabilsin. Kısıtlı izinli (chat/web) ve Beast Code
|
|
3936
|
+
oturumları hariç; tam izinli ('all') her oturuma açılır. */
|
|
3937
|
+
if (!bcAgent && perms.includes('all')) {
|
|
3938
|
+
const have = new Set(activeTools.map((t) => t.function.name));
|
|
3939
|
+
activeTools = [...activeTools, ...customtools.definitions().filter((t) => !have.has(t.function.name))];
|
|
3940
|
+
}
|
|
3923
3941
|
if (perms.length === 1 && perms[0] === 'chat') {
|
|
3924
3942
|
system +=
|
|
3925
3943
|
'\n\n# KISITLI MOD\nTüm araçların (komut, dosya, web, tarayıcı, hafıza) kapalı. Sadece yazarak cevap ver. ' +
|
|
@@ -4929,6 +4947,7 @@ const skills = require('./skills');
|
|
|
4929
4947
|
const s = this._load(sid);
|
|
4930
4948
|
if (s && !s.isBotDm) {
|
|
4931
4949
|
if (String(s.botId || '') !== want) this.setSessionBot(sid, want);
|
|
4950
|
+
this._toolSessionId = sid;
|
|
4932
4951
|
return sid;
|
|
4933
4952
|
}
|
|
4934
4953
|
}
|
|
@@ -4937,6 +4956,7 @@ const skills = require('./skills');
|
|
|
4937
4956
|
const sid = String(created.id);
|
|
4938
4957
|
this.setSessionBot(sid, want);
|
|
4939
4958
|
this.setSessionPerm(sid, target.perm || 'all');
|
|
4959
|
+
this._toolSessionId = sid;
|
|
4940
4960
|
try {
|
|
4941
4961
|
fs.writeFileSync(this._toolSessionFile, JSON.stringify({ id: sid, botId: want, at: nowIso() }));
|
|
4942
4962
|
} catch {}
|
|
@@ -6013,11 +6033,11 @@ const AGENT_DM_DEF = {
|
|
|
6013
6033
|
function: {
|
|
6014
6034
|
name: 'agent_dm',
|
|
6015
6035
|
description:
|
|
6016
|
-
'Send a short DM to another
|
|
6036
|
+
'Send a short DM to another agent or BOT (parallel agents, finance agents, bot chats like "Tool") to coordinate: share findings, ask status, warn about risk, hand off work, and MAKE JOINT DECISIONS. `to` = target session id, a bot name/id (e.g. "Tool", "Trader"), or a keyword from the agent title (e.g. "GOLD"). `topic` = short subject label — replies to the same topic stay in the SAME conversation thread, so ALWAYS reuse the topic you were DMed with when replying. `group` = optional group-chat name (e.g. "GOLD EKIP"): creates or reuses a group conversation, adds the target agent as a member, and your message is delivered to EVERY member — use groups when a decision needs multiple agents. `image` shares a chart/screen as an attachment the receiving agent REALLY sees and the AJAN DM panel shows: true = your last screenshot (fresh capture if none); a file path (e.g. the tool__mt5_shot "path") or a data URL = that exact image.',
|
|
6017
6037
|
parameters: {
|
|
6018
6038
|
type: 'object',
|
|
6019
6039
|
properties: {
|
|
6020
|
-
to: { type: 'string', description: 'Target
|
|
6040
|
+
to: { type: 'string', description: 'Target session id, bot name/id (e.g. "Tool") or a title keyword' },
|
|
6021
6041
|
topic: { type: 'string', description: 'Short subject label for the conversation thread, e.g. "GOLD pozisyon riski"' },
|
|
6022
6042
|
message: { type: 'string', description: 'Short message (1-3 sentences)' },
|
|
6023
6043
|
group: { type: 'string', description: 'Optional group-chat name — send to ALL members of that group (creates it on first use, adds the target agent)' },
|
|
@@ -6889,7 +6909,7 @@ Engine.prototype._agentDmSend = async function (fromSid, args) {
|
|
|
6889
6909
|
fromJob && fromJob.title
|
|
6890
6910
|
? String(fromJob.title)
|
|
6891
6911
|
: fromSess
|
|
6892
|
-
?
|
|
6912
|
+
? this._agentDmSessionTitle(fromSess)
|
|
6893
6913
|
: 'Ajan';
|
|
6894
6914
|
/* hedef çöz: oturum id YA DA başlık anahtar kelimesi (koşan ajanlar +
|
|
6895
6915
|
ANA OTURUM FALLBACK: chat oturumları this.cache'te yaşar — finance
|
|
@@ -6909,19 +6929,12 @@ Engine.prototype._agentDmSend = async function (fromSid, args) {
|
|
|
6909
6929
|
}
|
|
6910
6930
|
if (!target) {
|
|
6911
6931
|
const q = to.toLowerCase();
|
|
6912
|
-
|
|
6913
|
-
|
|
6914
|
-
|
|
6915
|
-
|
|
6916
|
-
|
|
6917
|
-
|
|
6918
|
-
const hay = [s.bgTitle, s.title, s.code, s.code ? 'beast · ' + s.code : ''].map((x) =>
|
|
6919
|
-
String(x || '').toLowerCase()
|
|
6920
|
-
);
|
|
6921
|
-
if (hay.some((x) => x && x.includes(q))) {
|
|
6922
|
-
target = sid;
|
|
6923
|
-
break;
|
|
6924
|
-
}
|
|
6932
|
+
target = this._agentDmFindSession(q, fromSid);
|
|
6933
|
+
if (!target) {
|
|
6934
|
+
/* CACHE SOĞUK OLABİLİR (uygulama yeni açıldı): oturum dosyalarını
|
|
6935
|
+
yükleyip bir kez daha ara — bot oturumu diskte olsa da bulunur. */
|
|
6936
|
+
try { this.listSessions(); } catch {}
|
|
6937
|
+
target = this._agentDmFindSession(q, fromSid);
|
|
6925
6938
|
}
|
|
6926
6939
|
}
|
|
6927
6940
|
if (String(target) === String(fromSid)) return { ok: false, error: 'kendine DM atılamaz' };
|
|
@@ -6943,7 +6956,7 @@ Engine.prototype._agentDmSend = async function (fromSid, args) {
|
|
|
6943
6956
|
const toTitle = target
|
|
6944
6957
|
? toJob
|
|
6945
6958
|
? String((toJob && toJob.title) || 'Ajan')
|
|
6946
|
-
:
|
|
6959
|
+
: this._agentDmSessionTitle(mainChatSess, 'Beast')
|
|
6947
6960
|
: '';
|
|
6948
6961
|
/* konu etiketi: aynı konudaki DM'ler panelde AYNI oturumda toplanır */
|
|
6949
6962
|
const topic = String((args && args.topic) || '').replace(/\s+/g, ' ').trim().slice(0, 60) || '(genel)';
|
|
@@ -7010,19 +7023,10 @@ Engine.prototype._agentDmSend = async function (fromSid, args) {
|
|
|
7010
7023
|
/* canlı event TEK SEFER — üye başına emit edilirse panelde çoğalır */
|
|
7011
7024
|
emitSafe(this, target || g.members[0], { type: 'agent-dm', ...dm });
|
|
7012
7025
|
for (const m of g.members) {
|
|
7013
|
-
if (m === String(fromSid)
|
|
7014
|
-
|
|
7015
|
-
|
|
7016
|
-
|
|
7017
|
-
`[AJAN DM (grup: "${g.title}") — ${dm.fromTitle} · konu: "${topic}"]\n${text}\n` +
|
|
7018
|
-
`(Cevap yalnızca aksiyon/karar GEREKİYORSA ver — agent_dm group: "${g.title}", to: "${dm.fromTitle}", topic: "${topic}"; teşekkür/onay yazma.)`,
|
|
7019
|
-
...(image ? { image } : {}),
|
|
7020
|
-
/* dm: AJAN DM'i; wake: gönderen koşan bir ajan DEĞİL (kullanıcı/chat) —
|
|
7021
|
-
sürekli ajan boştaysa turu beklemeden uyandırılır (ping-pong koruması) */
|
|
7022
|
-
dm: true,
|
|
7023
|
-
wake: !fromJob,
|
|
7024
|
-
});
|
|
7025
|
-
this.flushPendingReports(m);
|
|
7026
|
+
if (m === String(fromSid)) continue;
|
|
7027
|
+
/* wake: gönderen koşan bir ajan DEĞİL (kullanıcı/chat) —
|
|
7028
|
+
sürekli ajan boştaysa turu beklemeden uyandırılır */
|
|
7029
|
+
this._agentDmDeliver(dm, m, { groupTitle: g.title, wake: !fromJob });
|
|
7026
7030
|
}
|
|
7027
7031
|
this._persistAgentDms();
|
|
7028
7032
|
return {
|
|
@@ -7040,27 +7044,77 @@ Engine.prototype._agentDmSend = async function (fromSid, args) {
|
|
|
7040
7044
|
this._pushAgentDm(dm);
|
|
7041
7045
|
this._persistAgentDms();
|
|
7042
7046
|
emitSafe(this, target, { type: 'agent-dm', ...dm });
|
|
7043
|
-
|
|
7044
|
-
(this._pendingReports = this._pendingReports || []).push({
|
|
7045
|
-
parentId: target,
|
|
7046
|
-
text:
|
|
7047
|
-
`[AJAN DM — ${dm.fromTitle} · konu: "${topic}"]\n${text}\n` +
|
|
7048
|
-
`(Cevap yalnızca aksiyon/karar GEREKİYORSA ver — agent_dm to: "${dm.fromTitle}", topic: "${topic}"; teşekkür/onay yazma.)`,
|
|
7049
|
-
...(image ? { image } : {}),
|
|
7050
|
-
dm: true,
|
|
7051
|
-
wake: !fromJob,
|
|
7052
|
-
});
|
|
7053
|
-
this.flushPendingReports(target);
|
|
7054
|
-
}
|
|
7047
|
+
this._agentDmDeliver(dm, target, { wake: !fromJob });
|
|
7055
7048
|
return { ok: true, to: target, toTitle: dm.toTitle, topic };
|
|
7056
7049
|
} catch (e) {
|
|
7057
7050
|
return { ok: false, error: String((e && e.message) || e) };
|
|
7058
7051
|
}
|
|
7059
7052
|
};
|
|
7060
7053
|
|
|
7054
|
+
/* AJAN DM TESLİMİ: hedef ister koşan ajan (bg işi) ister düz/bot sohbet
|
|
7055
|
+
oturumu olsun mesaj GERÇEKTEN ulaşır. _pendingReports kuyruğu hedef
|
|
7056
|
+
meşgulse tur bitiminde düşer, boştaysa yeni tur açar. ESKİ HÂLDE yalnız
|
|
7057
|
+
bg işlerine teslim ediliyordu: bot sohbetine (ör. Tool) giden DM panelde
|
|
7058
|
+
kalıyor, bot hiç görmüyordu. Bot↔bot tur fırtınasına karşı çift başına
|
|
7059
|
+
pencere bütçesi vardır; bütçe dolunca mesaj yeni tur AÇMAZ, hedefin bir
|
|
7060
|
+
sonraki turunda önüne eklenir (sepet) — sonsuz ping-pong maliyeti olmaz. */
|
|
7061
|
+
Engine.prototype._agentDmDeliver = function (dm, target, opts = {}) {
|
|
7062
|
+
try {
|
|
7063
|
+
const m = String(target || '');
|
|
7064
|
+
if (!m || !dm) return false;
|
|
7065
|
+
const jobs = this._bgJobs || new Map();
|
|
7066
|
+
const job = jobs.get(m);
|
|
7067
|
+
const sess = job ? null : this.cache.get(m) || null;
|
|
7068
|
+
/* kayıtlı olmayan hedefe teslim yok — panelde kalır, sahte kuyruk birikmez */
|
|
7069
|
+
if (!job && !sess && !fs.existsSync(this._file(m))) return false;
|
|
7070
|
+
const groupTag = opts.groupTitle ? ` (grup: "${opts.groupTitle}")` : '';
|
|
7071
|
+
const replyTo = opts.groupTitle
|
|
7072
|
+
? `agent_dm group: "${opts.groupTitle}", to: "${dm.fromTitle}", topic: "${dm.topic}"`
|
|
7073
|
+
: `agent_dm to: "${dm.fromTitle}", topic: "${dm.topic}"`;
|
|
7074
|
+
const text =
|
|
7075
|
+
`[AJAN DM${groupTag} — ${dm.fromTitle} · konu: "${dm.topic}"]\n${dm.text}\n` +
|
|
7076
|
+
`(Cevap yalnızca aksiyon/karar GEREKİYORSA ver — ${replyTo}; teşekkür/onay yazma.)`;
|
|
7077
|
+
const rec = {
|
|
7078
|
+
parentId: m,
|
|
7079
|
+
text,
|
|
7080
|
+
...(dm.image ? { image: String(dm.image) } : {}),
|
|
7081
|
+
dm: true,
|
|
7082
|
+
wake: !!opts.wake,
|
|
7083
|
+
};
|
|
7084
|
+
/* bot↔bot sigorta: boş bot oturumunu her DM'de yeni turla uyandırma */
|
|
7085
|
+
if (sess && sess.botId && !this.isBusy(m) && !this._agentDmWakeAllowed(dm.from, m)) {
|
|
7086
|
+
this._agentDmBacklog = this._agentDmBacklog || new Map();
|
|
7087
|
+
const q = this._agentDmBacklog.get(m) || [];
|
|
7088
|
+
q.push({ text, ...(dm.image ? { image: String(dm.image) } : {}) });
|
|
7089
|
+
if (q.length > 20) q.splice(0, q.length - 20);
|
|
7090
|
+
this._agentDmBacklog.set(m, q);
|
|
7091
|
+
return true;
|
|
7092
|
+
}
|
|
7093
|
+
(this._pendingReports = this._pendingReports || []).push(rec);
|
|
7094
|
+
this.flushPendingReports(m);
|
|
7095
|
+
return true;
|
|
7096
|
+
} catch {
|
|
7097
|
+
return false;
|
|
7098
|
+
}
|
|
7099
|
+
};
|
|
7100
|
+
|
|
7101
|
+
/* Bot-ajan DM ping-pong sigortası: aynı çift 10 dk içinde 10 otomatik
|
|
7102
|
+
uyandırmayı aşarsa yeni tur AÇILMAZ (BEAST_DM_WAKE_LIMIT=0 kapatır). */
|
|
7103
|
+
Engine.prototype._agentDmWakeAllowed = function (a, b) {
|
|
7104
|
+
if (process.env.BEAST_DM_WAKE_LIMIT === '0') return true;
|
|
7105
|
+
const key = [String(a || ''), String(b || '')].sort().join('|');
|
|
7106
|
+
this._dmWakeLog = this._dmWakeLog || new Map();
|
|
7107
|
+
const now = Date.now();
|
|
7108
|
+
const arr = (this._dmWakeLog.get(key) || []).filter((t) => now - t < 10 * 60 * 1000);
|
|
7109
|
+
if (arr.length >= 10) { this._dmWakeLog.set(key, arr); return false; }
|
|
7110
|
+
arr.push(now);
|
|
7111
|
+
this._dmWakeLog.set(key, arr);
|
|
7112
|
+
return true;
|
|
7113
|
+
};
|
|
7114
|
+
|
|
7061
7115
|
/* AJAN DM görseli (dosyadan): png/jpg/webp/gif dosyasını data URL'e çevirir.
|
|
7062
7116
|
Send_file kapısına takılmadan (ör. tool__mt5_shot path'i) görsel paylaşımı;
|
|
7063
|
-
2.5MB üstü DM kaydını
|
|
7117
|
+
2.5MB üstü DM kaydını şişmesin diye atlanır. */
|
|
7064
7118
|
Engine.prototype._agentDmImageFromFile = function (p) {
|
|
7065
7119
|
try {
|
|
7066
7120
|
const fp = String(p || '').trim();
|
|
@@ -7110,6 +7164,51 @@ Engine.prototype._agentDmCaptureImage = async function (sid) {
|
|
|
7110
7164
|
return found;
|
|
7111
7165
|
};
|
|
7112
7166
|
|
|
7167
|
+
/* Oturum önbelleğinde DM hedefi ara (q küçük harf anahtar). Skor:
|
|
7168
|
+
tam id > bot adı/kimliği ya da tam kod > başlık/kod içerir.
|
|
7169
|
+
Eşitlikte KOŞAN oturum > Tool botunun kalıcı istek oturumu tercih edilir. */
|
|
7170
|
+
Engine.prototype._agentDmFindSession = function (q, fromSid) {
|
|
7171
|
+
let best = '';
|
|
7172
|
+
let bestScore = 0;
|
|
7173
|
+
for (const [sid, s] of this.cache) {
|
|
7174
|
+
if (sid === String(fromSid)) continue;
|
|
7175
|
+
if (!s || s.bgJob || s.isBotDm) continue;
|
|
7176
|
+
/* BOT OTURUMLARI DA HEDEF OLABİLİR: bot adı/kimliği ile eşleşir
|
|
7177
|
+
(ör. "Tool"); böylece Tool botuna DM gerçekten ulaşır — eski hâlde
|
|
7178
|
+
yalnız panelde kalıyordu. */
|
|
7179
|
+
const bot = s.botId && typeof this.resolveBot === 'function' ? this.resolveBot(s.botId) : null;
|
|
7180
|
+
const title = this._agentDmSessionTitle(s, '').toLowerCase();
|
|
7181
|
+
const hay = [s.bgTitle, s.title, s.code, title, bot ? bot.name : '', bot ? bot.id : '']
|
|
7182
|
+
.map((x) => String(x || '').toLowerCase())
|
|
7183
|
+
.filter(Boolean);
|
|
7184
|
+
let score = 0;
|
|
7185
|
+
if (String(sid).toLowerCase() === q) score = 4;
|
|
7186
|
+
else if (bot && (String(bot.id).toLowerCase() === q || String(bot.name).toLowerCase() === q)) score = 3;
|
|
7187
|
+
else if (s.code && String(s.code).toLowerCase() === q) score = 3;
|
|
7188
|
+
else if (hay.some((x) => x && x.includes(q))) score = 1;
|
|
7189
|
+
if (!score) continue;
|
|
7190
|
+
let pick = score * 10;
|
|
7191
|
+
if (this.ctrls.has(String(sid))) pick += 2;
|
|
7192
|
+
if (s.botId === 'tool' && String(this._toolSessionId || '') === String(sid)) pick += 1;
|
|
7193
|
+
if (pick > bestScore) { bestScore = pick; best = sid; }
|
|
7194
|
+
}
|
|
7195
|
+
return best;
|
|
7196
|
+
};
|
|
7197
|
+
|
|
7198
|
+
/* Oturumun AJAN DM başlığı: bot oturumunda bot adı (ör. "Tool"), düz sohbette
|
|
7199
|
+
"Beast · KOD". Başlık hem panelde kimin konuştuğunu netleştirir hem de cevap
|
|
7200
|
+
adreslemesinde kullanılır (karşı taraf bu başlıkla geri yazar). */
|
|
7201
|
+
Engine.prototype._agentDmSessionTitle = function (sess, fallback = 'Ajan') {
|
|
7202
|
+
if (!sess) return String(fallback);
|
|
7203
|
+
const code = String(sess.code || '');
|
|
7204
|
+
if (sess.botId && typeof this.resolveBot === 'function') {
|
|
7205
|
+
const b = this.resolveBot(sess.botId);
|
|
7206
|
+
if (b && b.name) return code ? String(b.name) + ' · ' + code : String(b.name);
|
|
7207
|
+
}
|
|
7208
|
+
const tail = String(sess.bgTitle || sess.title || code || '');
|
|
7209
|
+
return tail ? 'Beast · ' + tail : 'Beast';
|
|
7210
|
+
};
|
|
7211
|
+
|
|
7113
7212
|
/* İki ajanın ORTAK üyesi olduğu AÇIK grup var mı — 1:1 DM'leri grup içine
|
|
7114
7213
|
yönlendirirken kullanılır (ekip içi trafik tek thread'de toplanır). */
|
|
7115
7214
|
Engine.prototype._agentDmSharedGroup = function (a, b) {
|