beast-agent 2.62.1 → 2.62.2

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.62.1",
4
+ "version": "2.62.2",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
@@ -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.20 şu şablonu işler: {"id":"..","ts":..,"cmd":"shot","params":{file,width,height,symbol?,timeframe?,template?}}
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
- function syncSleep(ms) {
44
- try { Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms); }
45
- catch (e) { const t = Date.now(); while (Date.now() - t < ms) { /* busy wait */ } }
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
- syncSleep(200);
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
- syncSleep(400);
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
- syncSleep(300);
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
- syncSleep(250);
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() + 8000);
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
- syncSleep(250);
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
- try {
580
- const { execSync } = require('child_process');
581
- pcmd = execSync('powershell -NoProfile -Command "(Get-CimInstance Win32_Process -Filter \\"ProcessId=' +
582
- process.ppid + '\\").CommandLine"', { timeout: 5000 }).toString().trim();
583
- } catch (e) { pcmd = 'okunamadı: ' + String(e && e.message).slice(0, 120); }
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": 5,
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",
@@ -2858,37 +2858,45 @@ class Engine {
2858
2858
  }
2859
2859
  }
2860
2860
 
2861
- /* SÜREKLİ ajan DM INBOX (bkz. flushPendingReports): tur aralığında biriken
2862
- DM'ler yeni tur AÇMAZ; sıradaki planlı turun mesajına tek blok olarak
2863
- eklenir — ajan-ajan sonsuz DM ping-pong'u ve bağlam şişmesi engellenir. */
2861
+ /* SÜREKLİ ajan DM INBOX + BOT DM SEPETİ (bkz. flushPendingReports ve
2862
+ _agentDmDeliver): tur aralığında biriken DM'ler yeni tur AÇMAZ; sıradaki
2863
+ mesajın önüne tek blok olarak eklenir — ajan-ajan sonsuz DM ping-pong'u,
2864
+ tur fırtınası ve bağlam şişmesi engellenir. */
2864
2865
  try {
2866
+ const pendingDm = [];
2865
2867
  const jobRec = this._bgJobs && this._bgJobs.get(String(sessionId));
2866
2868
  if (jobRec && Array.isArray(jobRec.dmInbox) && jobRec.dmInbox.length) {
2867
- const inbox = jobRec.dmInbox.splice(0);
2868
- const boxText = inbox
2869
+ for (const x of jobRec.dmInbox.splice(0)) pendingDm.push(x);
2870
+ }
2871
+ if (this._agentDmBacklog && this._agentDmBacklog.size) {
2872
+ const q = this._agentDmBacklog.get(String(sessionId));
2873
+ if (q && q.length) for (const x of q.splice(0)) pendingDm.push(x);
2874
+ }
2875
+ if (pendingDm.length) {
2876
+ const boxText = pendingDm
2869
2877
  .map((x) => (typeof x === 'string' ? x : String((x && x.text) || '')))
2870
2878
  .join('\n\n')
2871
2879
  .slice(0, 6000);
2872
2880
  /* görselli DM'ler: son 2 görsel bu turun mesajına vision olarak eklenir */
2873
- const inboxImgs = inbox
2881
+ const dmImgs = pendingDm
2874
2882
  .map((x) => (x && typeof x === 'object' && /^data:image\//i.test(String(x.image || '')) ? String(x.image) : ''))
2875
2883
  .filter(Boolean)
2876
2884
  .slice(-2);
2877
2885
  const prefix =
2878
- `[BEKLEYEN AJAN DM'LERİ — ${inbox.length} mesaj]\n${boxText}\n` +
2886
+ `[BEKLEYEN AJAN DM'LERİ — ${pendingDm.length} mesaj]\n${boxText}\n` +
2879
2887
  `(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
2888
  if (typeof msg.content === 'string') {
2881
- msg.content = inboxImgs.length
2889
+ msg.content = dmImgs.length
2882
2890
  ? [
2883
2891
  { type: 'text', text: prefix + msg.content },
2884
- ...inboxImgs.map((u) => ({ type: 'image_url', image_url: { url: u } })),
2892
+ ...dmImgs.map((u) => ({ type: 'image_url', image_url: { url: u } })),
2885
2893
  ]
2886
2894
  : prefix + msg.content;
2887
2895
  } else if (Array.isArray(msg.content)) {
2888
2896
  if (msg.content[0] && typeof msg.content[0].text === 'string') {
2889
2897
  msg.content[0] = { ...msg.content[0], text: prefix + msg.content[0].text };
2890
2898
  }
2891
- for (const u of inboxImgs) msg.content.push({ type: 'image_url', image_url: { url: u } });
2899
+ for (const u of dmImgs) msg.content.push({ type: 'image_url', image_url: { url: u } });
2892
2900
  }
2893
2901
  }
2894
2902
  } catch {}
@@ -3843,14 +3851,11 @@ class Engine {
3843
3851
  );
3844
3852
  if (skillDef) toolsList = [...toolsList, skillDef];
3845
3853
  }
3846
- /* AJAN DM: koşan ajan oturumlarına (arka plan işleri + finance
3847
- ajanları) VE ana sohbete verilir — chat Beast'i de ajana DM atabilir;
3848
- müşteri bot oturumları (botId ≠ beast) ve botlar arası DM hariç */
3849
- if (
3850
- session &&
3851
- !session.isBotDm &&
3852
- (session.bgJob || session.finance || !session.botId || session.botId === 'beast')
3853
- ) {
3854
+ /* AJAN DM: koşan ajanlara (arka plan işleri + finance), ana sohbete VE
3855
+ bot sohbetlerine verilir — botlar birbirine iş atar/cevap verir
3856
+ (ör. Beast → Tool botu; Tool botu da sonucu DM ile geri yazar).
3857
+ Yalnız botlar arası DM pair oturumunda kapalı (döngü koruması). */
3858
+ if (session && !session.isBotDm) {
3854
3859
  toolsList = [...toolsList, AGENT_DM_DEF];
3855
3860
  }
3856
3861
  /* TOOL İSTEĞİ (cephane): tool botu hariç TÜM oturumlar — müşteri bot
@@ -3920,6 +3925,15 @@ class Engine {
3920
3925
  const allow = new Set(adef.tools);
3921
3926
  activeTools = activeTools.filter((t) => allow.has(t.function.name));
3922
3927
  }
3928
+ /* KİŞİSEL/VARSAYILAN TOOLLAR (tool__*) CEPHANE KURALI: bot skill
3929
+ whitelist'i (toolLimit) ve ajan beyaz listesi bunları düşürmesin —
3930
+ Tool botu onardığı aracı kendi oturumunda test edebilsin, diğer
3931
+ botlar da kullanabilsin. Kısıtlı izinli (chat/web) ve Beast Code
3932
+ oturumları hariç; tam izinli ('all') her oturuma açılır. */
3933
+ if (!bcAgent && perms.includes('all')) {
3934
+ const have = new Set(activeTools.map((t) => t.function.name));
3935
+ activeTools = [...activeTools, ...customtools.definitions().filter((t) => !have.has(t.function.name))];
3936
+ }
3923
3937
  if (perms.length === 1 && perms[0] === 'chat') {
3924
3938
  system +=
3925
3939
  '\n\n# KISITLI MOD\nTüm araçların (komut, dosya, web, tarayıcı, hafıza) kapalı. Sadece yazarak cevap ver. ' +
@@ -4929,6 +4943,7 @@ const skills = require('./skills');
4929
4943
  const s = this._load(sid);
4930
4944
  if (s && !s.isBotDm) {
4931
4945
  if (String(s.botId || '') !== want) this.setSessionBot(sid, want);
4946
+ this._toolSessionId = sid;
4932
4947
  return sid;
4933
4948
  }
4934
4949
  }
@@ -4937,6 +4952,7 @@ const skills = require('./skills');
4937
4952
  const sid = String(created.id);
4938
4953
  this.setSessionBot(sid, want);
4939
4954
  this.setSessionPerm(sid, target.perm || 'all');
4955
+ this._toolSessionId = sid;
4940
4956
  try {
4941
4957
  fs.writeFileSync(this._toolSessionFile, JSON.stringify({ id: sid, botId: want, at: nowIso() }));
4942
4958
  } catch {}
@@ -6013,11 +6029,11 @@ const AGENT_DM_DEF = {
6013
6029
  function: {
6014
6030
  name: 'agent_dm',
6015
6031
  description:
6016
- 'Send a short DM to another running agent (parallel agents / finance agents) to coordinate: share findings, ask status, warn about risk, hand off work, and MAKE JOINT DECISIONS. `to` = target session id OR a keyword from the agent title (e.g. "GOLD", "Trader"). `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.',
6032
+ '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
6033
  parameters: {
6018
6034
  type: 'object',
6019
6035
  properties: {
6020
- to: { type: 'string', description: 'Target agent session id or a title keyword' },
6036
+ to: { type: 'string', description: 'Target session id, bot name/id (e.g. "Tool") or a title keyword' },
6021
6037
  topic: { type: 'string', description: 'Short subject label for the conversation thread, e.g. "GOLD pozisyon riski"' },
6022
6038
  message: { type: 'string', description: 'Short message (1-3 sentences)' },
6023
6039
  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 +6905,7 @@ Engine.prototype._agentDmSend = async function (fromSid, args) {
6889
6905
  fromJob && fromJob.title
6890
6906
  ? String(fromJob.title)
6891
6907
  : fromSess
6892
- ? 'Beast · ' + String(fromSess.bgTitle || fromSess.title || fromSess.code || '')
6908
+ ? this._agentDmSessionTitle(fromSess)
6893
6909
  : 'Ajan';
6894
6910
  /* hedef çöz: oturum id YA DA başlık anahtar kelimesi (koşan ajanlar +
6895
6911
  ANA OTURUM FALLBACK: chat oturumları this.cache'te yaşar — finance
@@ -6909,19 +6925,12 @@ Engine.prototype._agentDmSend = async function (fromSid, args) {
6909
6925
  }
6910
6926
  if (!target) {
6911
6927
  const q = to.toLowerCase();
6912
- for (const [sid, s] of this.cache) {
6913
- if (sid === String(fromSid)) continue;
6914
- if (!s || s.bgJob || s.isBotDm) continue;
6915
- if (s.botId && s.botId !== 'beast') continue;
6916
- /* tam başlık da eşleşir: ajan DM'den gelen "Beast · KOD" başlığıyla
6917
- cevap verir — fromTitle formatı birebir tanınmalı */
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
- }
6928
+ target = this._agentDmFindSession(q, fromSid);
6929
+ if (!target) {
6930
+ /* CACHE SOĞUK OLABİLİR (uygulama yeni açıldı): oturum dosyalarını
6931
+ yükleyip bir kez daha ara — bot oturumu diskte olsa da bulunur. */
6932
+ try { this.listSessions(); } catch {}
6933
+ target = this._agentDmFindSession(q, fromSid);
6925
6934
  }
6926
6935
  }
6927
6936
  if (String(target) === String(fromSid)) return { ok: false, error: 'kendine DM atılamaz' };
@@ -6943,7 +6952,7 @@ Engine.prototype._agentDmSend = async function (fromSid, args) {
6943
6952
  const toTitle = target
6944
6953
  ? toJob
6945
6954
  ? String((toJob && toJob.title) || 'Ajan')
6946
- : String((mainChatSess && (mainChatSess.bgTitle || mainChatSess.title)) || 'Beast')
6955
+ : this._agentDmSessionTitle(mainChatSess, 'Beast')
6947
6956
  : '';
6948
6957
  /* konu etiketi: aynı konudaki DM'ler panelde AYNI oturumda toplanır */
6949
6958
  const topic = String((args && args.topic) || '').replace(/\s+/g, ' ').trim().slice(0, 60) || '(genel)';
@@ -7010,19 +7019,10 @@ Engine.prototype._agentDmSend = async function (fromSid, args) {
7010
7019
  /* canlı event TEK SEFER — üye başına emit edilirse panelde çoğalır */
7011
7020
  emitSafe(this, target || g.members[0], { type: 'agent-dm', ...dm });
7012
7021
  for (const m of g.members) {
7013
- if (m === String(fromSid) || !jobs.has(m)) continue;
7014
- (this._pendingReports = this._pendingReports || []).push({
7015
- parentId: m,
7016
- text:
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);
7022
+ if (m === String(fromSid)) continue;
7023
+ /* wake: gönderen koşan bir ajan DEĞİL (kullanıcı/chat) —
7024
+ sürekli ajan boştaysa turu beklemeden uyandırılır */
7025
+ this._agentDmDeliver(dm, m, { groupTitle: g.title, wake: !fromJob });
7026
7026
  }
7027
7027
  this._persistAgentDms();
7028
7028
  return {
@@ -7040,27 +7040,77 @@ Engine.prototype._agentDmSend = async function (fromSid, args) {
7040
7040
  this._pushAgentDm(dm);
7041
7041
  this._persistAgentDms();
7042
7042
  emitSafe(this, target, { type: 'agent-dm', ...dm });
7043
- if (jobs.has(target)) {
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
- }
7043
+ this._agentDmDeliver(dm, target, { wake: !fromJob });
7055
7044
  return { ok: true, to: target, toTitle: dm.toTitle, topic };
7056
7045
  } catch (e) {
7057
7046
  return { ok: false, error: String((e && e.message) || e) };
7058
7047
  }
7059
7048
  };
7060
7049
 
7050
+ /* AJAN DM TESLİMİ: hedef ister koşan ajan (bg işi) ister düz/bot sohbet
7051
+ oturumu olsun mesaj GERÇEKTEN ulaşır. _pendingReports kuyruğu hedef
7052
+ meşgulse tur bitiminde düşer, boştaysa yeni tur açar. ESKİ HÂLDE yalnız
7053
+ bg işlerine teslim ediliyordu: bot sohbetine (ör. Tool) giden DM panelde
7054
+ kalıyor, bot hiç görmüyordu. Bot↔bot tur fırtınasına karşı çift başına
7055
+ pencere bütçesi vardır; bütçe dolunca mesaj yeni tur AÇMAZ, hedefin bir
7056
+ sonraki turunda önüne eklenir (sepet) — sonsuz ping-pong maliyeti olmaz. */
7057
+ Engine.prototype._agentDmDeliver = function (dm, target, opts = {}) {
7058
+ try {
7059
+ const m = String(target || '');
7060
+ if (!m || !dm) return false;
7061
+ const jobs = this._bgJobs || new Map();
7062
+ const job = jobs.get(m);
7063
+ const sess = job ? null : this.cache.get(m) || null;
7064
+ /* kayıtlı olmayan hedefe teslim yok — panelde kalır, sahte kuyruk birikmez */
7065
+ if (!job && !sess && !fs.existsSync(this._file(m))) return false;
7066
+ const groupTag = opts.groupTitle ? ` (grup: "${opts.groupTitle}")` : '';
7067
+ const replyTo = opts.groupTitle
7068
+ ? `agent_dm group: "${opts.groupTitle}", to: "${dm.fromTitle}", topic: "${dm.topic}"`
7069
+ : `agent_dm to: "${dm.fromTitle}", topic: "${dm.topic}"`;
7070
+ const text =
7071
+ `[AJAN DM${groupTag} — ${dm.fromTitle} · konu: "${dm.topic}"]\n${dm.text}\n` +
7072
+ `(Cevap yalnızca aksiyon/karar GEREKİYORSA ver — ${replyTo}; teşekkür/onay yazma.)`;
7073
+ const rec = {
7074
+ parentId: m,
7075
+ text,
7076
+ ...(dm.image ? { image: String(dm.image) } : {}),
7077
+ dm: true,
7078
+ wake: !!opts.wake,
7079
+ };
7080
+ /* bot↔bot sigorta: boş bot oturumunu her DM'de yeni turla uyandırma */
7081
+ if (sess && sess.botId && !this.isBusy(m) && !this._agentDmWakeAllowed(dm.from, m)) {
7082
+ this._agentDmBacklog = this._agentDmBacklog || new Map();
7083
+ const q = this._agentDmBacklog.get(m) || [];
7084
+ q.push({ text, ...(dm.image ? { image: String(dm.image) } : {}) });
7085
+ if (q.length > 20) q.splice(0, q.length - 20);
7086
+ this._agentDmBacklog.set(m, q);
7087
+ return true;
7088
+ }
7089
+ (this._pendingReports = this._pendingReports || []).push(rec);
7090
+ this.flushPendingReports(m);
7091
+ return true;
7092
+ } catch {
7093
+ return false;
7094
+ }
7095
+ };
7096
+
7097
+ /* Bot-ajan DM ping-pong sigortası: aynı çift 10 dk içinde 10 otomatik
7098
+ uyandırmayı aşarsa yeni tur AÇILMAZ (BEAST_DM_WAKE_LIMIT=0 kapatır). */
7099
+ Engine.prototype._agentDmWakeAllowed = function (a, b) {
7100
+ if (process.env.BEAST_DM_WAKE_LIMIT === '0') return true;
7101
+ const key = [String(a || ''), String(b || '')].sort().join('|');
7102
+ this._dmWakeLog = this._dmWakeLog || new Map();
7103
+ const now = Date.now();
7104
+ const arr = (this._dmWakeLog.get(key) || []).filter((t) => now - t < 10 * 60 * 1000);
7105
+ if (arr.length >= 10) { this._dmWakeLog.set(key, arr); return false; }
7106
+ arr.push(now);
7107
+ this._dmWakeLog.set(key, arr);
7108
+ return true;
7109
+ };
7110
+
7061
7111
  /* AJAN DM görseli (dosyadan): png/jpg/webp/gif dosyasını data URL'e çevirir.
7062
7112
  Send_file kapısına takılmadan (ör. tool__mt5_shot path'i) görsel paylaşımı;
7063
- 2.5MB üstü DM kaydını şişirmesin diye atlanır. */
7113
+ 2.5MB üstü DM kaydını şişmesin diye atlanır. */
7064
7114
  Engine.prototype._agentDmImageFromFile = function (p) {
7065
7115
  try {
7066
7116
  const fp = String(p || '').trim();
@@ -7110,6 +7160,51 @@ Engine.prototype._agentDmCaptureImage = async function (sid) {
7110
7160
  return found;
7111
7161
  };
7112
7162
 
7163
+ /* Oturum önbelleğinde DM hedefi ara (q küçük harf anahtar). Skor:
7164
+ tam id > bot adı/kimliği ya da tam kod > başlık/kod içerir.
7165
+ Eşitlikte KOŞAN oturum > Tool botunun kalıcı istek oturumu tercih edilir. */
7166
+ Engine.prototype._agentDmFindSession = function (q, fromSid) {
7167
+ let best = '';
7168
+ let bestScore = 0;
7169
+ for (const [sid, s] of this.cache) {
7170
+ if (sid === String(fromSid)) continue;
7171
+ if (!s || s.bgJob || s.isBotDm) continue;
7172
+ /* BOT OTURUMLARI DA HEDEF OLABİLİR: bot adı/kimliği ile eşleşir
7173
+ (ör. "Tool"); böylece Tool botuna DM gerçekten ulaşır — eski hâlde
7174
+ yalnız panelde kalıyordu. */
7175
+ const bot = s.botId && typeof this.resolveBot === 'function' ? this.resolveBot(s.botId) : null;
7176
+ const title = this._agentDmSessionTitle(s, '').toLowerCase();
7177
+ const hay = [s.bgTitle, s.title, s.code, title, bot ? bot.name : '', bot ? bot.id : '']
7178
+ .map((x) => String(x || '').toLowerCase())
7179
+ .filter(Boolean);
7180
+ let score = 0;
7181
+ if (String(sid).toLowerCase() === q) score = 4;
7182
+ else if (bot && (String(bot.id).toLowerCase() === q || String(bot.name).toLowerCase() === q)) score = 3;
7183
+ else if (s.code && String(s.code).toLowerCase() === q) score = 3;
7184
+ else if (hay.some((x) => x && x.includes(q))) score = 1;
7185
+ if (!score) continue;
7186
+ let pick = score * 10;
7187
+ if (this.ctrls.has(String(sid))) pick += 2;
7188
+ if (s.botId === 'tool' && String(this._toolSessionId || '') === String(sid)) pick += 1;
7189
+ if (pick > bestScore) { bestScore = pick; best = sid; }
7190
+ }
7191
+ return best;
7192
+ };
7193
+
7194
+ /* Oturumun AJAN DM başlığı: bot oturumunda bot adı (ör. "Tool"), düz sohbette
7195
+ "Beast · KOD". Başlık hem panelde kimin konuştuğunu netleştirir hem de cevap
7196
+ adreslemesinde kullanılır (karşı taraf bu başlıkla geri yazar). */
7197
+ Engine.prototype._agentDmSessionTitle = function (sess, fallback = 'Ajan') {
7198
+ if (!sess) return String(fallback);
7199
+ const code = String(sess.code || '');
7200
+ if (sess.botId && typeof this.resolveBot === 'function') {
7201
+ const b = this.resolveBot(sess.botId);
7202
+ if (b && b.name) return code ? String(b.name) + ' · ' + code : String(b.name);
7203
+ }
7204
+ const tail = String(sess.bgTitle || sess.title || code || '');
7205
+ return tail ? 'Beast · ' + tail : 'Beast';
7206
+ };
7207
+
7113
7208
  /* İki ajanın ORTAK üyesi olduğu AÇIK grup var mı — 1:1 DM'leri grup içine
7114
7209
  yönlendirirken kullanılır (ekip içi trafik tek thread'de toplanır). */
7115
7210
  Engine.prototype._agentDmSharedGroup = function (a, b) {
@@ -1,24 +1,23 @@
1
1
  //+------------------------------------------------------------------+
2
- //| BeastFinance.mq5 — Beast Finance köprü EA'sı (v1.21) |
2
+ //| BeastFinance.mq5 — Beast Finance köprü EA'sı (v1.22) |
3
3
  //| - Heartbeat: MQL5\Files\beast_ea.json (durum + izinler + equity) |
4
4
  //| - Komut köprüsü: beast_cmd.json (Beast yazar) → beast_cmd_ack.json|
5
5
  //| - Entegrasyon/Screenshot: grafik üstü pano (Comment) + seviye |
6
6
  //| çizgileri — Beast beast_note.json ile besler; görsel ajan |
7
7
  //| computer_look ile chart screenshot'ında bu panoyu görür. |
8
+ //| NOT: Trade işlemleri Python köprüsünden yürür — bu EA emir AÇMAZ;|
9
+ //| bu yüzden <Trade\Trade.mqh> bağımlılığı YOKTUR (standart |
10
+ //| include klasörü eksik kurulumlarda EA derlenemiyordu). |
8
11
  //+------------------------------------------------------------------+
9
12
  #property copyright "Beast Agent"
10
13
  #property link "https://github.com/algokodcom/beast-agent"
11
- #property version "1.21"
14
+ #property version "1.22"
12
15
  #property description "Beast Finance köprü EA'sı — heartbeat + komut köprüsü + grafik panosu."
13
16
 
14
- #include <Trade\Trade.mqh>
15
-
16
17
  input string InpTag = "BeastFinance"; // Etiket
17
18
  input int InpHeartbeat = 5; // Kalp atışı (sn)
18
19
  input bool InpVerbose = true; // Grafik panosunu yaz
19
- input long InpMagic = 20260910; // Magic number
20
-
21
- CTrade gTrade;
20
+ input long InpMagic = 20260910; // Magic number (Python köprüsü kullanır)
22
21
 
23
22
  string BeatFile = "beast_ea.json";
24
23
  string CmdFile = "beast_cmd.json";
@@ -29,10 +28,33 @@ string LvlPrefix = "BeastLvl_";
29
28
  string gNoteText = "";
30
29
  string gNoteSymbol = "";
31
30
 
31
+ /* BEKLEYEN GÖRSEL İŞLERİ: ChartScreenShot ASENKRON üretir — komut bitince
32
+ hemen grafiği kapatmak/boyamak çekimi iptal edebiliyor. Bu yüzden geçici
33
+ grafiğin kapatılması ve aktif grafiğin shift ayarının geri yazılması bir
34
+ sonraki timer turuna ertelenir (PNG'nin diske yazılmasına süre tanınır). */
35
+ long gPendCloseChart = 0;
36
+ long gPendRestoreChart = 0;
37
+ long gPendShiftPrev = 0;
38
+ double gPendShiftSizePrev = 0;
39
+
40
+ void FlushPendingShot()
41
+ {
42
+ if(gPendCloseChart != 0)
43
+ {
44
+ ChartClose(gPendCloseChart); /* kapanmışsa zararsız (false döner) */
45
+ gPendCloseChart = 0;
46
+ }
47
+ if(gPendRestoreChart != 0)
48
+ {
49
+ ChartSetInteger(gPendRestoreChart, CHART_SHIFT, gPendShiftPrev != 0);
50
+ ChartSetDouble(gPendRestoreChart, CHART_SHIFT_SIZE, gPendShiftSizePrev);
51
+ ChartRedraw(gPendRestoreChart);
52
+ gPendRestoreChart = 0;
53
+ }
54
+ }
55
+
32
56
  int OnInit()
33
57
  {
34
- gTrade.SetExpertMagicNumber(InpMagic);
35
- gTrade.SetTypeFillingBySymbol(_Symbol);
36
58
  EventSetTimer(MathMax(1, InpHeartbeat));
37
59
  WriteBeat("init");
38
60
  RefreshPanel();
@@ -43,6 +65,7 @@ int OnInit()
43
65
  void OnDeinit(const int reason)
44
66
  {
45
67
  EventKillTimer();
68
+ FlushPendingShot();
46
69
  WriteBeat("deinit");
47
70
  ClearLevels();
48
71
  Comment("");
@@ -50,6 +73,11 @@ void OnDeinit(const int reason)
50
73
 
51
74
  void OnTimer()
52
75
  {
76
+ /* ÖNEMLİ SIRA: önce ÖNCEKİ shot'un artıkları toparlanır (geçici grafik
77
+ kapatılır, aktif grafiğin shift'i geri yazılır); sonra yeni komut işlenir.
78
+ Ters sırada olsaydı yeni açılan geçici grafik aynı turda kapatılıp
79
+ screenshot kaybolurdu. */
80
+ FlushPendingShot();
53
81
  ReadNote();
54
82
  WriteBeat("beat");
55
83
  ProcessCommands();
@@ -72,7 +100,7 @@ void WriteBeat(const string stage)
72
100
  {
73
101
  int h = FileOpen(BeatFile, FILE_WRITE|FILE_TXT|FILE_UNICODE);
74
102
  if(h == INVALID_HANDLE) return;
75
- string js = StringFormat("{\"ok\":true,\"ea\":\"BeastFinance\",\"version\":\"1.21\",\"tag\":\"%s\",\"stage\":\"%s\",\"time\":%d,\"server_time\":\"%s\",\"symbol\":\"%s\",\"period\":%d,\"equity\":%.2f,\"balance\":%.2f,\"terminal_trade_allowed\":%s,\"mql_trade_allowed\":%s,\"positions\":%d,\"note\":\"%s\"}",
103
+ string js = StringFormat("{\"ok\":true,\"ea\":\"BeastFinance\",\"version\":\"1.22\",\"tag\":\"%s\",\"stage\":\"%s\",\"time\":%d,\"server_time\":\"%s\",\"symbol\":\"%s\",\"period\":%d,\"equity\":%.2f,\"balance\":%.2f,\"terminal_trade_allowed\":%s,\"mql_trade_allowed\":%s,\"positions\":%d,\"note\":\"%s\"}",
76
104
  JStr(InpTag), stage, (int)TimeCurrent(), TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS),
77
105
  _Symbol, (int)Period(),
78
106
  AccountInfoDouble(ACCOUNT_EQUITY), AccountInfoDouble(ACCOUNT_BALANCE),
@@ -93,7 +121,7 @@ void RefreshPanel()
93
121
  Comment("");
94
122
  return;
95
123
  }
96
- string lines = "Beast Finance — BeastFinance v1.21\n";
124
+ string lines = "Beast Finance — BeastFinance v1.22\n";
97
125
  lines += _Symbol + " · " + EnumToString((ENUM_TIMEFRAMES)Period()) + " · " + TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS) + "\n";
98
126
  lines += StringFormat("Equity: %.2f · Balance: %.2f · Acik pozisyon: %d\n",
99
127
  AccountInfoDouble(ACCOUNT_EQUITY), AccountInfoDouble(ACCOUNT_BALANCE), PositionsTotal());
@@ -198,6 +226,7 @@ void ApplyLevels(const string raw)
198
226
  }
199
227
  if(price > 0)
200
228
  {
229
+ if(n >= ArraySize(names)) break; /* taşma koruması: sınırı aşan seviye atlanır */
201
230
  string name = LvlPrefix + IntegerToString(n);
202
231
  names[n] = name;
203
232
  if(ObjectFind(0, name) < 0) ObjectCreate(0, name, OBJ_HLINE, 0, 0, price);
@@ -240,6 +269,22 @@ void WriteAck(const string id, const string cmd, const bool ok, const string err
240
269
  FileClose(h);
241
270
  }
242
271
 
272
+ /* Komut dosyasını YALNIZ hâlâ bizim id'mizi taşıyorsa sil — EA işlerken
273
+ yazılmış YENİ bir komutun yanlışlıkla silinmesi komut kaybı (ea_no_ack)
274
+ üretiyordu. */
275
+ void DeleteCmdIfId(const string id)
276
+ {
277
+ if(!FileIsExist(CmdFile)) return;
278
+ int h = FileOpen(CmdFile, FILE_READ|FILE_TXT|FILE_UNICODE);
279
+ if(h == INVALID_HANDLE) return;
280
+ string raw = "";
281
+ while(!FileIsEnding(h)) raw += FileReadString(h);
282
+ FileClose(h);
283
+ if(StringLen(raw) == 0) return;
284
+ string cur = JsonGet(raw, "id");
285
+ if(StringLen(cur) == 0 || StringCompare(cur, id) == 0) FileDelete(CmdFile);
286
+ }
287
+
243
288
  void ProcessCommands()
244
289
  {
245
290
  if(!FileIsExist(CmdFile)) return;
@@ -352,15 +397,17 @@ void ProcessCommands()
352
397
  }
353
398
 
354
399
  /* SON MUM SAĞ KENARA YAPIŞMASIN: grafik "shift" payı (%12 sağ boşluk).
400
+ CHART_SHIFT integer, CHART_SHIFT_SIZE ise DOUBLE özelliktir (10-50) —
401
+ yanlış API (ChartGet/SetInteger) EA'yı derlenmez hale getiriyordu.
355
402
  Aktif grafikte önceki ayar çekimden SONRA geri yazılır — kullanıcının
356
- grafiği kalıcı değişmez; geçici grafik zaten kapanır. */
357
- long shiftPrev = ChartGetInteger(cid, CHART_SHIFT);
358
- long shiftSizePrev = ChartGetInteger(cid, CHART_SHIFT_SIZE);
403
+ grafiği kalıcı değişmez; geçici grafik PNG yazılana kadar AÇIK kalır. */
404
+ long shiftPrev = ChartGetInteger(cid, CHART_SHIFT);
405
+ double shiftSizePrev = ChartGetDouble(cid, CHART_SHIFT_SIZE);
359
406
 
360
407
  if(ok)
361
408
  {
362
409
  ChartSetInteger(cid, CHART_SHIFT, true);
363
- ChartSetInteger(cid, CHART_SHIFT_SIZE, 12);
410
+ ChartSetDouble(cid, CHART_SHIFT_SIZE, 12.0);
364
411
  ChartRedraw(cid);
365
412
  Sleep(1200); /* mumlar/olcek otursun */
366
413
  ChartRedraw(cid);
@@ -375,14 +422,24 @@ void ProcessCommands()
375
422
  result = StringFormat("{\"file\":\"%s\",\"width\":%d,\"height\":%d,\"symbol\":\"%s\",\"period\":%d,\"temp_chart\":%s,\"template_applied\":%s,\"theme_ok\":%s,\"bg\":%d}",
376
423
  JStr(file), w, hh, symWant, (int)tfUse, (tempChart ? "true" : "false"), (tplApplied ? "true" : "false"), (themeOk ? "true" : "false"), bgNow);
377
424
  }
378
- if(!tempChart)
379
- {
380
- ChartSetInteger(cid, CHART_SHIFT, shiftPrev != 0);
381
- ChartSetInteger(cid, CHART_SHIFT_SIZE, shiftSizePrev);
382
- ChartRedraw(cid);
383
- }
384
425
  }
385
- if(tempChart) ChartClose(cid);
426
+ /* ACK GARANTİSİ: ChartScreenShot görüntüyü ASENKRON üretir; ack'i
427
+ geciktirmek tool tarafında sahte "ea_no_ack" hatalarına yol açıyordu.
428
+ Ack burada hemen yazılır; PNG'yi tool ayrıca bekler. Geçici grafik ve
429
+ aktif grafiğin shift ayarı bir sonraki timer turunda toparlanır. */
430
+ WriteAck(id, cmd, ok, err, result);
431
+ DeleteCmdIfId(id);
432
+ if(tempChart)
433
+ {
434
+ gPendCloseChart = cid;
435
+ }
436
+ else
437
+ {
438
+ gPendRestoreChart = cid;
439
+ gPendShiftPrev = shiftPrev;
440
+ gPendShiftSizePrev = shiftSizePrev;
441
+ }
442
+ return; /* ack bu yolda yazıldı — ortak kuyruğa tekrar girmesin */
386
443
  }
387
444
  else
388
445
  {
@@ -390,7 +447,7 @@ void ProcessCommands()
390
447
  err = "bilinmeyen komut: " + cmd;
391
448
  }
392
449
  WriteAck(id, cmd, ok, err, result);
393
- FileDelete(CmdFile);
450
+ DeleteCmdIfId(id);
394
451
  }
395
452
 
396
453
  /* BeastFinance.tpl acik tema renklerini grafige uygular (sablon bulunamazsa da garanti) */
package/src/main.js CHANGED
@@ -2355,6 +2355,7 @@ function botToolSet(cfg) {
2355
2355
  'watcher_add', 'watcher_list', 'watcher_remove',
2356
2356
  'tool_request', /* CEPHANE: eksik aracı TOOL botuna yazdırma hakkı HER botta */
2357
2357
  'channel_send', /* İLK MESAJ: allow listteki kişilere kendiliğinden yazma hakkı HER botta */
2358
+ 'agent_dm', /* BOTLAR ARASI DM: botlar birbirine iş atar/cevap verir (ör. Beast → Tool) */
2358
2359
  ]);
2359
2360
  if (s.web_search) { set.add('web_search'); set.add('http_fetch'); set.add('webfetch'); set.add('deep_search'); }
2360
2361
  if (s.browser) {