beast-agent 2.49.1 → 2.51.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.49.1",
4
+ "version": "2.51.0",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
package/src/agent/bots.js CHANGED
@@ -107,7 +107,7 @@ const DEFAULT_BOT_SEEDS = [
107
107
  name: 'Trader',
108
108
  icon: '📊',
109
109
  prompt:
110
- 'Sen BEAST Trader botsun: disiplinli bir trading analisti. Gerçek emir göndermez, API anahtarı istemez/kullanmazsın — yalnız genel fiyat API\'lerinden okur, paper trade tutarsın. Her kararda sembol, zaman dilimi, giriş/stop/hedef seviyeleri ve risk yüzdesini açıkça belirtirsin; sinyal zayıfsa "BEKLE" dersin. İzleyici (watcher) kurarak sürekli izlersin. Grafik doğrulaması: tool__mt5_shot ile MT5 grafiğinden GERÇEK PNG al ve grafiği gör (yoksa computer_look; metin-model isen ocr_read source:"screen"/"browser"); web grafiklerinde browser_screenshot kullan. İhtiyacın olan özel grafik aracı yoksa tool_request ile TOOL botuna yazdır — tool yazımı onun işidir. Beast Finance MT5 terminaline bağlandığında BeastFinance uzman danışmanı İLK GRAFİĞE OTOMATİK kurulur (AutoTrading + grafik panosu + seviye çizgileri); grafik/entegrasyon işlerinde bu EA ve mt5_ea köprüsü kullanılır — elle ekleme varsayma.',
110
+ 'Sen BEAST Trader botsun: disiplinli bir trading analisti. Gerçek emir göndermez, API anahtarı istemez/kullanmazsın — yalnız genel fiyat API\'lerinden okur, paper trade tutarsın. Her kararda sembol, zaman dilimi, giriş/stop/hedef seviyeleri ve risk yüzdesini açıkça belirtirsin; sinyal zayıfsa "BEKLE" dersin. İzleyici (watcher) kurarak sürekli izlersin. Grafik doğrulaması: tool__mt5_shot ile MT5 grafiğinden GERÇEK PNG al ve grafiği gör (timeframes:["M1","M15"] ile çoklu periyot, symbols:["GOLD","EURUSD"] ile çoklu sembol TEK karede; yoksa computer_look; metin-model isen ocr_read source:"screen"/"browser"); web grafiklerinde browser_screenshot kullan. İhtiyacın olan özel grafik aracı yoksa tool_request ile TOOL botuna yazdır — tool yazımı onun işidir. Beast Finance MT5 terminaline bağlandığında BeastFinance uzman danışmanı İLK GRAFİĞE OTOMATİK kurulur (AutoTrading + grafik panosu + seviye çizgileri); grafik/entegrasyon işlerinde bu EA ve mt5_ea köprüsü kullanılır — elle ekleme varsayma.',
111
111
  },
112
112
  {
113
113
  id: 'coder',
@@ -2,15 +2,20 @@
2
2
  /* mt5_shot — MT5 grafiğinden GERÇEK PNG screenshot (BeastFinance EA "shot" komutu)
3
3
  stdin: JSON args -> stdout: SADECE JSON sonuç
4
4
 
5
- Girdi : {symbol?, timeframe?, timeframes?:["M1","M15"], layout?:"h"|"v", width?, height?, file?, template?, timeoutSec?, outdir?, diag?, embed?}
6
- Çıktı : {ok:true, path, symbol, timeframe, ...} | {ok:false, err, error, cmdId, filesDir, ...}
5
+ Girdi : {symbol?, symbols?:["GOLD","EURUSD"], timeframe?, timeframes?:["M1","M15"], layout?:"h"|"v", width?, height?, file?, template?, timeoutSec?, outdir?, diag?, embed?}
6
+ Çıktı : {ok:true, path, symbol, symbols, timeframe, timeframes, panels:[...], ...} | {ok:false, err, error, cmdId, filesDir, ...}
7
7
  err kodları: no_terminal | ea_offline | cmd_write_fail | ea_no_ack | ea_error | png_yok | timeout | stitch_fail | stitch_write_fail
8
8
 
9
+ ÇOKLU SEMBOL: symbols:["GOLD","EURUSD","BTCUSD"] (2-4 sembol) verilirse her
10
+ sembolden bir kare çekilir ve TEK PNG'de birleştirilir; timeframes ile
11
+ birlikte verilirse sembol × periyot ızgarası kurulur (en fazla 6 kare).
12
+ İzleme listesinin tamamı TEK görselde ekibe gönderilebilir.
13
+
9
14
  ÇOKLU PERİYOT: timeframes:["M1","M15"] (2-3 adet) verilirse her periyot ayrı
10
15
  çekilir ve TEK PNG'de birleştirilir (layout:"h" soldan sağa — varsayılan,
11
- "v" üstten alta). Panellerin sol üstüne "SEMBOL · PERİYOT" etiketi çizilir;
12
- sonuçta panels[{timeframe, position, path}] hangi tarafın hangi periyot
13
- olduğunu söyler. tf2:"M15" kısayolu da (timeframe + tf2) kabul edilir.
16
+ "v" üstten alta). Panellerin SAĞ ÜSTüne "SEMBOL · PERİYOT" etiketi çizilir;
17
+ sonuçta panels[{symbol, timeframe, position, path}] hangi tarafın hangi
18
+ sembol/periyot olduğunu söyler. tf2:"M15" kısayolu da (timeframe + tf2) kabul edilir.
14
19
 
15
20
  Notlar:
16
21
  - beast_cmd.json'a {id, ts, cmd, params} yazılır (UTF-16 BOM; EA FILE_UNICODE okur).
@@ -42,6 +47,7 @@ function syncSleep(ms) {
42
47
 
43
48
  let CTX = { cmdId: null, filesDir: null, started: Date.now() };
44
49
  let ARGS = {};
50
+ let WATCHDOG = null;
45
51
 
46
52
  function out(o) {
47
53
  try { fs.writeSync(1, JSON.stringify(o) + '\n'); }
@@ -180,6 +186,44 @@ function normalizeTfs(a) {
180
186
  return out.slice(0, 3); /* 80 sn watchdog bütçesi: en fazla 3 panel */
181
187
  }
182
188
 
189
+ /* çoklu sembol listesi: ["GOLD","EURUSD"] ya da "GOLD,EURUSD";
190
+ symbol alanı da listeye katılır (symbol + symbols birlikte verilebilir) */
191
+ function normalizeSymbols(a) {
192
+ const out = [];
193
+ const add = (x) => {
194
+ const s = String(x == null ? '' : x).trim().toUpperCase();
195
+ if (s && !out.includes(s)) out.push(s);
196
+ };
197
+ add(a.symbol);
198
+ let s = a.symbols;
199
+ if (typeof s === 'string') s = s.split(/[,\s|+/]+/);
200
+ if (Array.isArray(s)) for (const x of s) add(x);
201
+ return out.slice(0, 4);
202
+ }
203
+
204
+ /* PANEL PLANI: symbols × timeframes çarpımı → sırayla çekilecek kareler.
205
+ - symbols 2+ → her sembolden bir kare (timeframes da verilmişse her periyot)
206
+ - tek sembol + timeframes 2+ → eski çoklu periyot davranışı
207
+ - tek karelik işler plana girmez: [] döner (tek çekim yolu kullanılır)
208
+ Dönüş: [{symbol, timeframe}] — en fazla MAX_PANELS kare (watchdog bütçesi) */
209
+ const MAX_PANELS = 6;
210
+ function buildPlan(a) {
211
+ const A = a || ARGS;
212
+ const symbols = normalizeSymbols(A);
213
+ const tfs = normalizeTfs(A);
214
+ const out = [];
215
+ const push = (symbol, timeframe) => {
216
+ if (out.length < MAX_PANELS) out.push({ symbol, timeframe });
217
+ };
218
+ if (symbols.length >= 2) {
219
+ const per = tfs.length ? tfs : [null];
220
+ for (const s of symbols) for (const tf of per) push(s, tf);
221
+ return out;
222
+ }
223
+ if (tfs.length >= 2) for (const tf of tfs) push(symbols[0] || null, tf);
224
+ return out;
225
+ }
226
+
183
227
  function sanitizeFile(name, fallback) {
184
228
  let f = (name && String(name).trim()) ? path.basename(String(name).trim()) : fallback;
185
229
  if (!/\.(png|gif|bmp|jpg|jpeg)$/i.test(f)) f += '.png';
@@ -275,18 +319,22 @@ function embedData(src, size) {
275
319
  return dataUrl;
276
320
  }
277
321
 
278
- /* ---- ÇOKLU PERİYOT: aynı sembolün N zaman dilimini TEK PNG'de birleştir ----
279
- Paneller sırayla çekilir (kilit zaten alınmış — ack çakışması yok), sol üste
280
- "SEMBOL · PERİYOT" etiketi çizilir; soldan sağa (layout:"v" ile üstten alta). */
281
- async function runMulti(tfList, filesDir, cmdFile, ackFile, beatFile, timeoutSec, diag) {
322
+ /* ---- ÇOKLU KARE: plan (sembol × periyot) sırayla çekilir ve TEK PNG'de
323
+ birleştirilir — kilit zaten alınmış (ack çakışması yok); her panelin SAĞ
324
+ ÜSTüne "SEMBOL · PERİYOT" etiketi çizilir; soldan sağa (layout:"v" ile
325
+ üstten alta). plan: [{symbol, timeframe}] — boş alan = aktif grafik. */
326
+ async function runPanels(plan, filesDir, cmdFile, ackFile, beatFile, timeoutSec, diag) {
282
327
  let compose = null;
283
328
  try { compose = require('./stitch.js').compose; } catch (e) {}
284
329
  if (!compose) return { err: 'stitch_fail', error: 'stitch.js bulunamadı (mt5_shot güncel kurulum gerekiyor)' };
285
330
 
286
- const n = tfList.length;
331
+ const n = plan.length;
287
332
  const baseId = 'S' + Date.now().toString(36);
288
333
  const fileArg = ARGS.file && String(ARGS.file).trim() ? String(ARGS.file).trim() : '';
289
- const base = fileArg ? fileArg.replace(/\.[a-z0-9]+$/i, '') : 'beast_mtf_' + baseId;
334
+ const uniqSym = [...new Set(plan.map((p) => p.symbol).filter(Boolean))];
335
+ const base = fileArg
336
+ ? fileArg.replace(/\.[a-z0-9]+$/i, '')
337
+ : (uniqSym.length > 1 ? 'beast_symbols_' : 'beast_mtf_') + baseId;
290
338
  const perAck = Math.max(5, Math.min(15, Math.floor(45 / n)));
291
339
  const width = Number(ARGS.width) > 0 ? Math.min(10000, Math.round(Number(ARGS.width))) : 1600;
292
340
  const height = Number(ARGS.height) > 0 ? Math.min(10000, Math.round(Number(ARGS.height))) : 900;
@@ -294,32 +342,36 @@ async function runMulti(tfList, filesDir, cmdFile, ackFile, beatFile, timeoutSec
294
342
 
295
343
  const panels = [];
296
344
  const cmdIds = [];
297
- let symbol = ARGS.symbol ? String(ARGS.symbol).toUpperCase() : null;
298
345
  for (let i = 0; i < n; i++) {
299
- const tf = tfList[i];
346
+ const spec = plan[i] || {};
347
+ const symArg = spec.symbol ? String(spec.symbol).toUpperCase() : null;
348
+ const tfArg = spec.timeframe ? String(spec.timeframe).toUpperCase() : null;
300
349
  const id = baseId + 'p' + (i + 1);
301
- const params = { file: sanitizeFile(base + '_' + tf, 'beast_shot_' + id), width, height, timeframe: tf };
302
- if (symbol) params.symbol = symbol;
350
+ const tag = [symArg, tfArg].filter(Boolean).join('_') || 'aktif';
351
+ const params = { file: sanitizeFile(base + '_' + tag, 'beast_shot_' + id), width, height };
352
+ if (tfArg) params.timeframe = tfArg;
353
+ if (symArg) params.symbol = symArg;
303
354
  if (ARGS.template !== undefined) params.template = String(ARGS.template);
304
355
  cmdIds.push(id);
305
356
  const r = captureOne({ id, file: params.file, params, filesDir, cmdFile, ackFile, beatFile, timeoutSec: perAck, diag });
306
357
  if (!r.ok) {
307
358
  return {
308
359
  err: r.err,
309
- error: '[' + tf + '] ' + r.error,
310
- extra: Object.assign({ panel: tf, cmdId: id, requested: { symbol: params.symbol || null, timeframe: tf } }, r.extra || {})
360
+ error: '[' + tag + '] ' + r.error,
361
+ extra: Object.assign({ panel: tag, cmdId: id, requested: { symbol: symArg, timeframe: tfArg } }, r.extra || {})
311
362
  };
312
363
  }
313
364
  const cp = copyOut(r.src, r.shotFile, ARGS.outdir);
314
- panels.push({ tf, src: r.src, file: r.shotFile, size: r.size, res: r.res, pubPath: cp.pubPath, beastPath: cp.beastPath });
315
- if (!symbol) symbol = (r.res && r.res.symbol) ? String(r.res.symbol) : null;
365
+ const tfDone = (r.res && r.res.period ? tfName(r.res.period) : null) || tfArg || null;
366
+ const symDone = (r.res && r.res.symbol ? String(r.res.symbol).toUpperCase() : null) || symArg || null;
367
+ panels.push({ spec, sym: symDone, tf: tfDone, src: r.src, file: r.shotFile, size: r.size, res: r.res, pubPath: cp.pubPath, beastPath: cp.beastPath });
316
368
  }
317
369
 
318
- /* birleştir */
370
+ /* birleştir: etiketler çekimden SONRA gerçek sembol/periyotla kurulur */
319
371
  let composed = null;
320
372
  try {
321
373
  composed = await compose(
322
- panels.map((p) => ({ path: p.src, label: (symbol ? symbol + ' · ' : '') + p.tf })),
374
+ panels.map((p) => ({ path: p.src, label: [p.sym, p.tf].filter(Boolean).join(' · ') || 'aktif grafik' })),
323
375
  { layout }
324
376
  );
325
377
  } catch (e) { composed = { ok: false, error: String((e && e.message) || e) }; }
@@ -327,19 +379,21 @@ async function runMulti(tfList, filesDir, cmdFile, ackFile, beatFile, timeoutSec
327
379
  return { err: 'stitch_fail', error: 'görüntüler birleştirilemedi: ' + ((composed && composed.error) || 'bilinmeyen') };
328
380
  }
329
381
 
330
- const outFile = sanitizeFile(fileArg, 'beast_mtf_' + baseId + '.png');
382
+ const outFile = sanitizeFile(fileArg, base + '.png');
331
383
  const pathMt5 = path.join(filesDir, outFile);
332
384
  try { fs.writeFileSync(pathMt5, composed.buffer); }
333
385
  catch (e) { return { err: 'stitch_write_fail', error: 'birleşik PNG yazılamadı: ' + e.message }; }
334
386
  const size = composed.buffer.length;
335
387
  const cp = copyOut(pathMt5, outFile, ARGS.outdir);
336
388
  const dataUrl = embedData(pathMt5, size);
389
+ const symDone = [...new Set(panels.map((p) => p.sym).filter(Boolean))];
390
+ const tfList = [...new Set(plan.map((p) => p.timeframe).filter(Boolean))];
337
391
 
338
392
  return {
339
393
  ok: true,
340
394
  payload: {
341
395
  ok: true,
342
- path: cp.pubPath || cp.beastPath || pathMt5, // send_file'a verilecek yol (birleşik)
396
+ path: cp.pubPath || cp.beastPath || pathMt5, // send_file / agent_dm image:"<path>"
343
397
  path_mt5: pathMt5,
344
398
  path_beast: cp.beastPath,
345
399
  file: outFile,
@@ -348,10 +402,12 @@ async function runMulti(tfList, filesDir, cmdFile, ackFile, beatFile, timeoutSec
348
402
  ...(dataUrl ? { __injectImage: dataUrl } : {}),
349
403
  cmdIds,
350
404
  filesDir,
351
- symbol: symbol || null,
405
+ symbol: symDone.length === 1 ? symDone[0] : null,
406
+ symbols: symDone,
352
407
  timeframes: tfList,
353
408
  layout,
354
409
  panels: panels.map((p, i) => ({
410
+ symbol: p.sym,
355
411
  timeframe: p.tf,
356
412
  position: layout === 'h'
357
413
  ? (i === 0 ? 'left' : (i === n - 1 ? 'right' : 'middle'))
@@ -363,7 +419,12 @@ async function runMulti(tfList, filesDir, cmdFile, ackFile, beatFile, timeoutSec
363
419
  })),
364
420
  width: composed.width,
365
421
  height: composed.height,
366
- requested: { symbol: ARGS.symbol ? String(ARGS.symbol).toUpperCase() : null, timeframes: tfList, width, height, layout },
422
+ requested: {
423
+ symbol: ARGS.symbol ? String(ARGS.symbol).toUpperCase() : null,
424
+ symbols: Array.isArray(ARGS.symbols) ? ARGS.symbols.map((s) => String(s).toUpperCase()) : (ARGS.symbols || null),
425
+ timeframes: tfList,
426
+ width, height, layout
427
+ },
367
428
  heartbeat_age_sec: heartbeatAge(beatFile),
368
429
  ms: Date.now() - CTX.started,
369
430
  pubErr: cp.pubErr || undefined
@@ -394,10 +455,11 @@ async function run() {
394
455
  const locked = acquireLock(Math.min(15000, timeoutSec * 500));
395
456
  if (!locked) console.error('[mt5_shot] uyarı: kilit alınamadı, yine de devam ediliyor');
396
457
 
397
- /* ÇOKLU PERİYOT: timeframes:["M1","M15"] → tek karede yan yana */
398
- const tfList = normalizeTfs(ARGS);
399
- if (tfList.length >= 2) {
400
- const r = await runMulti(tfList, filesDir, cmdFile, ackFile, beatFile, timeoutSec, diag);
458
+ /* ÇOKLU KARE: symbols:["GOLD","EURUSD"] ve/veya timeframes:["M1","M15"]
459
+ → sırayla çekilip tek PNG'de etiketli birleştirilir */
460
+ const plan = buildPlan();
461
+ if (plan.length >= 2) {
462
+ const r = await runPanels(plan, filesDir, cmdFile, ackFile, beatFile, timeoutSec, diag);
401
463
  if (!r.ok) return fail(r.err, r.error, r.extra);
402
464
  return out(r.payload);
403
465
  }
@@ -546,24 +608,32 @@ function finish() {
546
608
  .then(run)
547
609
  .catch((e) => fail('timeout', 'beklenmeyen hata: ' + (e && e.message ? e.message : String(e))));
548
610
  }
549
- try {
550
- process.stdin.setEncoding('utf8');
551
- process.stdin.on('data', (d) => {
552
- raw += d;
553
- if (silenceTimer) clearTimeout(silenceTimer);
554
- // JSON tamamlandıysa EOF beklemeden başla; değilse kısa bir sessizlik penceresi daha bekle
555
- const o = parseArgs(raw);
556
- if (raw.trim() && Object.keys(o).length) return begin(finish);
557
- silenceTimer = setTimeout(() => begin(finish), 1200);
558
- });
559
- process.stdin.on('end', () => begin(finish));
560
- process.stdin.on('error', () => begin(finish));
561
- process.stdin.on('close', () => begin(finish));
562
- process.stdin.resume();
563
- setTimeout(() => begin(finish), 2500); // stdin hiç açılmaz/kapanmazsa da devam et (asılmaya izin yok)
564
- } catch (e) { begin(finish); }
565
-
566
- /* watchdog: hiçbir koşulda 80 sn üzeri asılı kalma */
567
- const WATCHDOG = setTimeout(() => {
568
- fail('timeout', 'mt5_shot ' + Math.round((Date.now() - CTX.started) / 1000) + ' sn içinde tamamlanamadı (watchdog) — ack/PNG beklemesi aşıldı');
569
- }, HARD_MS);
611
+
612
+ function main() {
613
+ try {
614
+ process.stdin.setEncoding('utf8');
615
+ process.stdin.on('data', (d) => {
616
+ raw += d;
617
+ if (silenceTimer) clearTimeout(silenceTimer);
618
+ // JSON tamamlandıysa EOF beklemeden başla; değilse kısa bir sessizlik penceresi daha bekle
619
+ const o = parseArgs(raw);
620
+ if (raw.trim() && Object.keys(o).length) return begin(finish);
621
+ silenceTimer = setTimeout(() => begin(finish), 1200);
622
+ });
623
+ process.stdin.on('end', () => begin(finish));
624
+ process.stdin.on('error', () => begin(finish));
625
+ process.stdin.on('close', () => begin(finish));
626
+ process.stdin.resume();
627
+ setTimeout(() => begin(finish), 2500); // stdin hiç açılmaz/kapanmazsa da devam et (asılmaya izin yok)
628
+ } catch (e) { begin(finish); }
629
+
630
+ /* watchdog: hiçbir koşulda 80 sn üzeri asılı kalma */
631
+ WATCHDOG = setTimeout(() => {
632
+ fail('timeout', 'mt5_shot ' + Math.round((Date.now() - CTX.started) / 1000) + ' sn içinde tamamlanamadı (watchdog) — ack/PNG beklemesi aşıldı');
633
+ }, HARD_MS);
634
+ }
635
+
636
+ /* saf yardımcılar test için dışa açılır; runner yalnız doğrudan çalıştırılınca başlar */
637
+ module.exports = { normalizeTfs, normalizeSymbols, buildPlan, sanitizeFile };
638
+
639
+ if (require.main === module) main();
@@ -97,16 +97,41 @@ async function compose(panels, opts) {
97
97
  if (layout === 'h') ctx.fillRect(x - GAP, 0, GAP, H);
98
98
  else ctx.fillRect(0, y - GAP, W, GAP);
99
99
  }
100
- /* etiket: sol üstte koyu şerit + parlak metin */
100
+ /* etiket: SAĞ ÜST köşede BÜYÜK zaman dilimi (üstünde küçük sembol) —
101
+ görsel ajan hangi panelin hangi periyot olduğunu bakışta okur */
101
102
  if (p.label) {
102
103
  try {
103
- ctx.font = pickFont(20);
104
- const tw = Math.min(cellW - 24, Math.ceil(ctx.measureText(p.label).width) + 20);
105
- ctx.fillStyle = 'rgba(0,0,0,0.78)';
106
- ctx.fillRect(x + 6, y + 6, Math.max(60, tw), LABEL_H);
107
- ctx.fillStyle = '#00e676';
104
+ const parts = String(p.label).split('·').map((s) => s.trim()).filter(Boolean);
105
+ const tf = parts.length > 1 ? parts[1] : parts[0];
106
+ const sym = parts.length > 1 ? parts[0] : '';
107
+ const fsTf = Math.max(22, Math.round(cellW / 44)); /* 1600 px panel → 36 px */
108
+ const fsSym = Math.max(12, Math.round(fsTf * 0.45));
109
+ const padX = Math.round(fsTf * 0.55);
110
+ const padY = Math.round(fsTf * 0.42);
111
+ const lineGap = Math.round(fsSym * 0.45);
108
112
  ctx.textBaseline = 'middle';
109
- ctx.fillText(p.label, x + 15, y + 6 + LABEL_H / 2 + 1);
113
+ ctx.textAlign = 'right';
114
+ ctx.font = pickFont(fsTf);
115
+ const tw = ctx.measureText(tf).width;
116
+ ctx.font = pickFont(fsSym);
117
+ const sw = sym ? ctx.measureText(sym).width : 0;
118
+ const boxW = Math.ceil(Math.max(tw, sw) + padX * 2);
119
+ const boxH = Math.ceil(padY * 2 + fsTf + (sym ? fsSym + lineGap : 0));
120
+ const bx = x + cellW - boxW - 10;
121
+ const by = y + 10;
122
+ ctx.fillStyle = 'rgba(0,0,0,0.80)';
123
+ ctx.fillRect(bx, by, boxW, boxH);
124
+ ctx.fillStyle = '#00e676';
125
+ ctx.fillRect(bx, by, boxW, 3); /* üst aksan çizgisi */
126
+ if (sym) {
127
+ ctx.font = pickFont(fsSym);
128
+ ctx.fillStyle = '#c9d2d9';
129
+ ctx.fillText(sym, bx + boxW - padX, by + padY + fsSym / 2);
130
+ }
131
+ ctx.font = pickFont(fsTf);
132
+ ctx.fillStyle = '#00e676';
133
+ ctx.fillText(tf, bx + boxW - padX, by + padY + (sym ? fsSym + lineGap : 0) + fsTf / 2);
134
+ ctx.textAlign = 'start';
110
135
  } catch (e) {}
111
136
  }
112
137
  });
@@ -1,19 +1,20 @@
1
1
  {
2
2
  "name": "mt5_shot",
3
- "defaultsVersion": 2,
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 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 üstüne \"SEMBOL · PERİYOT\" etiketi çizilir — solda/sağda hangi periyot olduğu görüntüden de 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). 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.",
3
+ "defaultsVersion": 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",
7
7
  "properties": {
8
- "symbol": { "type": "string", "description": "Boş = aktif grafik sembolü (örn GOLD)" },
8
+ "symbol": { "type": "string", "description": "Tek sembol (örn GOLD); boş = aktif grafik sembolü. Çoklu sembol için symbols kullan" },
9
+ "symbols": { "type": "array", "items": { "type": "string" }, "description": "ÇOKLU SEMBOL TEK GÖRÜNTÜ: örn [\"GOLD\",\"EURUSD\",\"BTCUSD\"] — her sembolden bir kare çekilip etiketli tek PNG'de birleştirilir (2-4 sembol; timeframes ile birlikte sembol × periyot ızgarası, en fazla 6 kare)" },
9
10
  "timeframe": { "type": "string", "description": "M1|M5|M15|M30|H1|H4|D1 (boş = aktif periyot)" },
10
- "timeframes": { "type": "array", "items": { "type": "string" }, "description": "ÇOKLU PERİYOT TEK GÖRÜNTÜ: örn [\"M1\",\"M15\"] — sırayla çekilip tek PNG'de birleştirilir (2-3 adet; soldan sağa)" },
11
+ "timeframes": { "type": "array", "items": { "type": "string" }, "description": "ÇOKLU PERİYOT TEK GÖRÜNTÜ: örn [\"M1\",\"M15\"] — sırayla çekilip tek PNG'de birleştirilir (2-3 adet; soldan sağa); symbols ile birlikte ızgara" },
11
12
  "tf2": { "type": "string", "description": "İkinci periyot kısayolu (timeframe ile birlikte): örn timeframe M1 + tf2 M15" },
12
- "layout": { "type": "string", "description": "Çoklu periyot yerleşimi: h = yan yana (varsayılan), v = alt alta" },
13
+ "layout": { "type": "string", "description": "Çoklu kare yerleşimi: h = yan yana (varsayılan), v = alt alta" },
13
14
  "width": { "type": "number", "description": "Panel başına görüntü genişliği (varsayılan 1600)" },
14
15
  "height": { "type": "number", "description": "Panel başına görüntü yüksekliği (varsayılan 900)" },
15
- "file": { "type": "string", "description": "Opsiyonel dosya adı (varsayılan beast_shot_<id>.png; çoklu periyotta birleşik dosyanın adı)" },
16
- "timeoutSec": { "type": "number", "description": "Ack/PNG bekleme süresi sn (varsayılan 20, 5-70; çoklu periyotta panel başına 15 sn tavan)" },
16
+ "file": { "type": "string", "description": "Opsiyonel dosya adı (varsayılan beast_shot_<id>.png; çoklu karede birleşik dosyanın adı)" },
17
+ "timeoutSec": { "type": "number", "description": "Ack/PNG bekleme süresi sn (varsayılan 20, 5-70; çoklu karede panel başına 15 sn tavan)" },
17
18
  "outdir": { "type": "string", "description": "PNG'nin kopyalanacağı klasör (varsayılan <kullanıcı>\\beast_shots — send_file buradan gönderebilir)" },
18
19
  "diag": { "type": "boolean", "description": "Ack gelmezse teşhis amaçlı ping at (varsayılan true)" },
19
20
  "embed": { "type": "boolean", "description": "PNG'yi ajana görsel olarak enjekte et (varsayılan true; false = yalnız dosya yolu döner)" }
@@ -1970,7 +1970,7 @@ class Engine {
1970
1970
  macro:
1971
1971
  'ROL: MAKRO AJANI 🌍 — büyük resim uzmanısın, İşlem AÇMAZSIN (mt5_trade/mt5_pending/mt5_close KULLANMA). Her turda web_search ile güncel makro manşetleri + ekonomik takvim riskleri (faiz, CPI, jeopolitik); DXY/altın/petrol bağıntılarını odak sembollere çevir; sembol başına yön eğilimi + TEMKİN/BEKLE notu ver.',
1972
1972
  visual:
1973
- 'ROL: GÖRSEL ANALİZ AJANI 👁 — grafik/ekran görüntüsü uzmanısın, İşlem AÇMAZSIN (mt5_trade/mt5_pending/mt5_close KULLANMA). MT5 grafiği için ÖNCE tool__mt5_shot kullan (BeastFinance EA "shot": gerçek grafik PNG + görsel SONRAKİ TURDA gözüne gelir; symbol/timeframe vererek başka sembol/periyot çekebilirsin; dönen path send_file ile gönderilebilir); gerekirse EA panosuna mt5_ea action:"note" ile plan/seviye yaz. web grafiklerinde browser_open + browser_screenshot; bilgisayar ekranı için computer_look; görsel göremeyen metin-model isen ocr_read (source:"screen"/"browser") kullan. Gördüğün yapıyı yorumla: trend, formasyon, mum yapısı, destek-direnç ve SL/TP çizgileri; sembol başına GÖRSEL TEYİT + AL/SAT/BEKLE notu üret. Önemli bulguyu GÖRSELİYLE paylaş: agent_dm image:true (son ekran görüntün) ya da image:"<mt5_shot path>" ile ANA TRADER\'a/ekibe gönder — karşı ajan görüntüyü GERÇEKTEN görür, AJAN DM panelinde açılır.',
1973
+ 'ROL: GÖRSEL ANALİZ AJANI 👁 — grafik/ekran görüntüsü uzmanısın, İşlem AÇMAZSIN (mt5_trade/mt5_pending/mt5_close KULLANMA). MT5 grafiği için ÖNCE tool__mt5_shot kullan (BeastFinance EA "shot": gerçek grafik PNG + görsel SONRAKİ TURDA gözüne gelir; symbol/timeframe vererek başka sembol/periyot çekebilirsin; dönen path send_file ile gönderilebilir); gerekirse EA panosuna mt5_ea action:"note" ile plan/seviye yaz. ÇOKLU SEMBOL: symbols:["GOLD","EURUSD","BTCUSD"] ile izleme listesindeki tüm semboller TEK çağrıda çekilip etiketli TEK PNG\'de birleştirilir — ayrı ayrı 3 DM atmak yerine bu birleşik görseli gönder. web grafiklerinde browser_open + browser_screenshot; bilgisayar ekranı için computer_look; görsel göremeyen metin-model isen ocr_read (source:"screen"/"browser") kullan. Gördüğün yapıyı yorumla: trend, formasyon, mum yapısı, destek-direnç ve SL/TP çizgileri; sembol başına GÖRSEL TEYİT + AL/SAT/BEKLE notu üret. Önemli bulguyu GÖRSELİYLE paylaş: agent_dm image:true (son ekran görüntün) ya da image:"<mt5_shot path>" ile ANA TRADER\'a/ekibe gönder — karşı ajan görüntüyü GERÇEKTEN görür, AJAN DM panelinde açılır. NOT: image:true DAİMA oturumdaki EN SON görseli yollar — her sembolün fotoğrafını ayrı göndermen gerekirse dönen path\'leri kullan (image:"<path>").',
1974
1974
  })[String((session && session.financeRole) || '')] || '';
1975
1975
  /* ROL → SKILL eşleştirmesi (ayarlar modalı): rol başına TEK ve ZORUNLU
1976
1976
  skill; ANA TRADER için ayrıca PLAYBOOK skill'i (roleSkills.trader).
@@ -2010,7 +2010,7 @@ class Engine {
2010
2010
  (roleBlockFull ? roleBlockFull + '\n' : '') +
2011
2011
  (teamLine || '') +
2012
2012
  '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_ea (BeastFinance grafik panosu: status/ping/chart/note), mt5_trade (piyasa emri), mt5_close (kapat), mt5_modify (SL/TP), mt5_pending (bekleyen emir), mt5_cancel (emir iptal).\n' +
2013
- 'KİŞİSEL MT5 ARAÇLARI (VARSAYILAN KURULU — tool__* tüm oturumlarda çağrılabilir): tool__mt5_shot (grafikten GERÇEK PNG + görsel ajana enjekte — grafik işlerinde BİRİNCİL; timeframes:["M1","M15"] ile çoklu periyot TEK karede soldan sağa), tool__mt5_barlar2 (OHLC mumlar, köprüsüz), tool__mt5_m15_m5 (GOLD M15+M5 hazır paket), tool__mt5_fiyat, tool__mt5_durum (hesap+pozisyon+emir tek çağrı), tool__mt5_gecmis, tool__mt5_pozisyon_gecmis, tool__mt5_sltp, tool__mt5_kapat, tool__mt5_bekleyen, tool__mt5_emir, tool__mt5_emir_iptal.\n' +
2013
+ 'KİŞİSEL MT5 ARAÇLARI (VARSAYILAN KURULU — tool__* tüm oturumlarda çağrılabilir): tool__mt5_shot (grafikten GERÇEK PNG + görsel ajana enjekte — grafik işlerinde BİRİNCİL; timeframes:["M1","M15"] ile çoklu periyot, symbols:["GOLD","EURUSD","BTCUSD"] ile çoklu sembol TEK karede), tool__mt5_barlar2 (OHLC mumlar, köprüsüz), tool__mt5_m15_m5 (GOLD M15+M5 hazır paket), tool__mt5_fiyat, tool__mt5_durum (hesap+pozisyon+emir tek çağrı), tool__mt5_gecmis, tool__mt5_pozisyon_gecmis, tool__mt5_sltp, tool__mt5_kapat, tool__mt5_bekleyen, tool__mt5_emir, tool__mt5_emir_iptal.\n' +
2014
2014
  'BEASTFINANCE EA (OTOMATİK GRAFİK UZMANI — ENTEGRASYON KANALI): MT5 terminaline bağlanıldığında BeastFinance uzman danışmanı İLK GRAFİĞE OTOMATİK yüklenir, AutoTrading izni açılır (kurulum sistem tarafından yapılır; elle ekleme gerekmez). Grafik panosu + seviye çizgileri + dosya köprüsü (beast_ea.json / beast_cmd.json / beast_note.json) bu EA üzerinden yürür: entegrasyon işlerinde (status/ping/chart/note) mt5_ea kullan; ekran görüntüsü işlerinde tool__mt5_shot ("shot" komutu — gerçek PNG + ajan görseli) birincildir. Yazdığın not ve çizgiler tool__mt5_shot ve computer_look screenshot\'ında GÖRÜNÜR.\n' +
2015
2015
  'YETKİLERİN (AÇIK — çekinmeden kullan):\n' +
2016
2016
  '- skill: kurulu SKILL.md kataloğunu oku ve uygula — tool yazmadan ÖNCE skill("tool-yazma"), MT5 tarafı işlerden ÖNCE skill("mql5") oku ve prosedürüne birebir uy.\n' +
@@ -2018,7 +2018,7 @@ class Engine {
2018
2018
  '- MQL5: MT5 tarafında script/gösterge/EA yaz (write_file), metaeditor64.exe /compile ile derle, MQL5\\Files dosya köprüsüyle veriyi Beast\'e taşı; kullanıcıya çalıştırma adımını açıkça söyle.\n' +
2019
2019
  '- Yerleşik araçlar: run_command, python_run, read_file/write_file/edit_file, web_search/deep_search, browser_*, computer_look (ekran görüntüsü) — hepsi açık.\n' +
2020
2020
  'GRAFİK & GÖRSEL DOĞRULAMA (karar öncesi — vazgeçilmez):\n' +
2021
- '- tool__mt5_shot: MT5 grafiğinin GERÇEK PNG\'si (BeastFinance EA "shot" komutu). Görsel SONRAKİ TURDA gözüne gelir; aktif grafikte Beast panosu + mt5_ea note ile çizilen seviyeler GÖRÜNÜR. symbol/timeframe ver → EA geçici grafik açıp o sembol/periyottan çeker; dönen path send_file ile kullanıcıya gönderilebilir. Grafik analizinde İLK tercih budur. ÇOKLU ZAMAN DİLİMİ: timeframes:["M1","M15"] (2-3 periyot, layout:"v" alt alta) → periyotlar sırayla çekilip TEK PNG\'de birleştirilir ve her panelin üstüne "SEMBOL · PERİYOT" etiketi çizilir; solda/sağda hangisi olduğunu görüntüden OKURSUN — trend uyumu/MTF teyidi için bunu kullan (gerçek kapanışlarla).\n' +
2021
+ '- tool__mt5_shot: MT5 grafiğinin GERÇEK PNG\'si (BeastFinance EA "shot" komutu). Görsel SONRAKİ TURDA gözüne gelir; aktif grafikte Beast panosu + mt5_ea note ile çizilen seviyeler GÖRÜNÜR. symbol/timeframe ver → EA geçici grafik açıp o sembol/periyottan çeker; dönen path send_file ile kullanıcıya gönderilebilir. Grafik analizinde İLK tercih budur. ÇOKLU SEMBOL: symbols:["GOLD","EURUSD","BTCUSD"] (2-4 sembol) → her sembolden bir kare çekilip TEK PNG\'de birleştirilir (izleme listesinin tamamı TEK görselle paylaşılır; sembol başına ayrı DM atmaya gerek yok); timeframes ile birlikte verilirse sembol × periyot ızgarası kurulur (en fazla 6 kare, her panel "SEMBOL · PERİYOT" etiketli). ÇOKLU ZAMAN DİLİMİ: timeframes:["M1","M15"] (2-3 periyot, layout:"v" alt alta) → periyotlar sırayla çekilip TEK PNG\'de birleştirilir; solda/sağda hangisi olduğunu görüntüden OKURSUN — trend uyumu/MTF teyidi için bunu kullan (gerçek kapanışlarla).\n' +
2022
2022
  '- mt5_ea action:"note": grafiğe kısa plan metni + yatay seviye çizgileri yazar (destek/direnç/SL/TP) — sonra tool__mt5_shot ile çekip seviyelerin doğru yerde olduğunu GÖRSEL doğrula.\n' +
2023
2023
  '- computer_look: tüm masaüstü ekranı (MT5 dışı pencereler dahil); browser_screenshot: web grafikleri (TradingView/Investing) — browser_open ile aç.\n' +
2024
2024
  '- Görsel göremiyorsan (metin-model) ocr_read (source:"screen"/"browser") ile grafikteki fiyat/seviyeleri metne çevir.\n' +
@@ -3688,7 +3688,9 @@ class Engine {
3688
3688
  /* SÜREKLİ ajan (Beast Finance): DM/rapor teslimi YENİ TUR AÇMAZ — aksi
3689
3689
  halde ajanlar birbirine DM attıkça sonsuz tur döngüsü doğar (bağlam
3690
3690
  şişer, ajan hiç dinlenmez). Mesaj inbox'a yazılır; bir sonraki PLANLI
3691
- turda send() tarafından tek blok halinde enjekte edilir. */
3691
+ turda send() tarafından tek blok halinde enjekte edilir.
3692
+ İSTİSNA: kullanıcı/chat kaynaklı DM'ler (wake) onDmQueued kancasıyla
3693
+ ajanı HEMEN uyandırabilir — ajan-ajan trafiği beklemede kalır. */
3692
3694
  const continuous = !!(job && job.continuous);
3693
3695
  const rest = [];
3694
3696
  for (const r of this._pendingReports) {
@@ -3701,6 +3703,11 @@ class Engine {
3701
3703
  ...(r.image ? { image: String(r.image) } : {}),
3702
3704
  });
3703
3705
  if (job.dmInbox.length > 20) job.dmInbox.splice(0, job.dmInbox.length - 20);
3706
+ /* kullanıcı/chat DM'i: sürekli ajan boştaysa turu beklemeden uyandır
3707
+ (main.js finWakeAgent); ajan-ajan DM'lerinde tetiklenmez */
3708
+ if (r.dm && r.wake) {
3709
+ try { if (typeof this.onDmQueued === 'function') this.onDmQueued(job, r); } catch {}
3710
+ }
3704
3711
  continue;
3705
3712
  }
3706
3713
  if (this.isBusy(sessionId)) { rest.push(r); continue; }
@@ -6973,6 +6980,10 @@ Engine.prototype._agentDmSend = async function (fromSid, args) {
6973
6980
  `[AJAN DM (grup: "${g.title}") — ${dm.fromTitle} · konu: "${topic}"]\n${text}\n` +
6974
6981
  `(Cevap yalnızca aksiyon/karar GEREKİYORSA ver — agent_dm group: "${g.title}", to: "${dm.fromTitle}", topic: "${topic}"; teşekkür/onay yazma.)`,
6975
6982
  ...(image ? { image } : {}),
6983
+ /* dm: AJAN DM'i; wake: gönderen koşan bir ajan DEĞİL (kullanıcı/chat) —
6984
+ sürekli ajan boştaysa turu beklemeden uyandırılır (ping-pong koruması) */
6985
+ dm: true,
6986
+ wake: !fromJob,
6976
6987
  });
6977
6988
  this.flushPendingReports(m);
6978
6989
  }
@@ -6999,6 +7010,8 @@ Engine.prototype._agentDmSend = async function (fromSid, args) {
6999
7010
  `[AJAN DM — ${dm.fromTitle} · konu: "${topic}"]\n${text}\n` +
7000
7011
  `(Cevap yalnızca aksiyon/karar GEREKİYORSA ver — agent_dm to: "${dm.fromTitle}", topic: "${topic}"; teşekkür/onay yazma.)`,
7001
7012
  ...(image ? { image } : {}),
7013
+ dm: true,
7014
+ wake: !fromJob,
7002
7015
  });
7003
7016
  this.flushPendingReports(target);
7004
7017
  }
@@ -1,5 +1,5 @@
1
1
  //+------------------------------------------------------------------+
2
- //| BeastFinance.mq5 — Beast Finance köprü EA'sı (v1.20) |
2
+ //| BeastFinance.mq5 — Beast Finance köprü EA'sı (v1.21) |
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 |
@@ -8,7 +8,7 @@
8
8
  //+------------------------------------------------------------------+
9
9
  #property copyright "Beast Agent"
10
10
  #property link "https://github.com/algokodcom/beast-agent"
11
- #property version "1.20"
11
+ #property version "1.21"
12
12
  #property description "Beast Finance köprü EA'sı — heartbeat + komut köprüsü + grafik panosu."
13
13
 
14
14
  #include <Trade\Trade.mqh>
@@ -72,7 +72,7 @@ void WriteBeat(const string stage)
72
72
  {
73
73
  int h = FileOpen(BeatFile, FILE_WRITE|FILE_TXT|FILE_UNICODE);
74
74
  if(h == INVALID_HANDLE) return;
75
- string js = StringFormat("{\"ok\":true,\"ea\":\"BeastFinance\",\"version\":\"1.20\",\"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\"}",
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\"}",
76
76
  JStr(InpTag), stage, (int)TimeCurrent(), TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS),
77
77
  _Symbol, (int)Period(),
78
78
  AccountInfoDouble(ACCOUNT_EQUITY), AccountInfoDouble(ACCOUNT_BALANCE),
@@ -93,7 +93,7 @@ void RefreshPanel()
93
93
  Comment("");
94
94
  return;
95
95
  }
96
- string lines = "Beast Finance — BeastFinance v1.20\n";
96
+ string lines = "Beast Finance — BeastFinance v1.21\n";
97
97
  lines += _Symbol + " · " + EnumToString((ENUM_TIMEFRAMES)Period()) + " · " + TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS) + "\n";
98
98
  lines += StringFormat("Equity: %.2f · Balance: %.2f · Acik pozisyon: %d\n",
99
99
  AccountInfoDouble(ACCOUNT_EQUITY), AccountInfoDouble(ACCOUNT_BALANCE), PositionsTotal());
@@ -351,8 +351,16 @@ void ProcessCommands()
351
351
  }
352
352
  }
353
353
 
354
+ /* SON MUM SAĞ KENARA YAPIŞMASIN: grafik "shift" payı (%12 sağ boşluk).
355
+ 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);
359
+
354
360
  if(ok)
355
361
  {
362
+ ChartSetInteger(cid, CHART_SHIFT, true);
363
+ ChartSetInteger(cid, CHART_SHIFT_SIZE, 12);
356
364
  ChartRedraw(cid);
357
365
  Sleep(1200); /* mumlar/olcek otursun */
358
366
  ChartRedraw(cid);
@@ -367,6 +375,12 @@ void ProcessCommands()
367
375
  result = StringFormat("{\"file\":\"%s\",\"width\":%d,\"height\":%d,\"symbol\":\"%s\",\"period\":%d,\"temp_chart\":%s,\"template_applied\":%s,\"theme_ok\":%s,\"bg\":%d}",
368
376
  JStr(file), w, hh, symWant, (int)tfUse, (tempChart ? "true" : "false"), (tplApplied ? "true" : "false"), (themeOk ? "true" : "false"), bgNow);
369
377
  }
378
+ if(!tempChart)
379
+ {
380
+ ChartSetInteger(cid, CHART_SHIFT, shiftPrev != 0);
381
+ ChartSetInteger(cid, CHART_SHIFT_SIZE, shiftSizePrev);
382
+ ChartRedraw(cid);
383
+ }
370
384
  }
371
385
  if(tempChart) ChartClose(cid);
372
386
  }
package/src/main.js CHANGED
@@ -3551,10 +3551,14 @@ function reloadBackend() {
3551
3551
  })();
3552
3552
  }
3553
3553
  },
3554
- });
3554
+ });
3555
+ /* AJAN DM UYANDIRMA: kullanıcı/chat kaynaklı DM sürekli finance ajanına
3556
+ düşünce sıradaki planlı turu BEKLEMEZ — ajan boştaysa tur hemen başlar
3557
+ (ajan-ajan DM'lerinde engine kancayı çağırmaz; ping-pong korunur). */
3558
+ engine.onDmQueued = (job) => { try { finWakeAgent(job && job.id); } catch {} };
3555
3559
  /* PANEL RUN köprüsü: ajanın panel_run aracı → ÇALIŞTIR panelindeki yönetilen
3556
3560
  süreç koşucusu (sandbox:run IPC ile aynı makine). */
3557
- engine.sbRunHook = sbRunStartManaged;
3561
+ engine.sbRunHook = sbRunStartManaged;
3558
3562
  /* SANDBOX KÖPRÜSÜ: ana sohbetteki ajanın sandbox_repo aracı → panel ile aynı
3559
3563
  makine (indir/kur/başlat/durdur); kullanıcı chatte repo linki bırakınca
3560
3564
  ajan kendisi indirip kurabilir. */
@@ -10942,6 +10946,18 @@ function finAgentRound(sid) {
10942
10946
  .catch(() => launch(teamPrefix));
10943
10947
  }
10944
10948
 
10949
+ /* AJAN DM UYANDIRMA (engine.onDmQueued): kullanıcı/chat kaynaklı DM sürekli
10950
+ ajanın inbox'ına düştüğünde tur zamanlayıcısını beklemeden turu başlatır.
10951
+ Ajan meşgulse dokunmaz — dmInbox zaten sıradaki güvenli noktada okunur. */
10952
+ function finWakeAgent(sid) {
10953
+ const id = String(sid || '');
10954
+ const agent = financeState.agents.get(id);
10955
+ if (!agent || !engine) return;
10956
+ if (engine.isBusy(id)) return;
10957
+ clearTimeout(agent.timer);
10958
+ agent.timer = setTimeout(() => { try { finAgentRound(id); } catch {} }, 300);
10959
+ }
10960
+
10945
10961
  /* Tur/durum sonları: sürekli ajan döngüsünü besle; kullanıcı iptalinde kapat */
10946
10962
  function finFlushOnDone(ev) {
10947
10963
  if (!ev || (ev.type !== 'done' && ev.type !== 'error')) return;
@@ -549,6 +549,12 @@
549
549
  </div>
550
550
  </div>
551
551
 
552
+ <!-- DM görselleri: tıkla → tam boy görüntüleyici (Esc / tıkla kapat) -->
553
+ <div id="imgOverlay" hidden>
554
+ <img id="imgOverlayImg" alt="görsel" />
555
+ <button id="imgOverlayClose" title="Kapat (Esc)">&#x2715;&#xFE0E;</button>
556
+ </div>
557
+
552
558
  <div id="toolOverlay" hidden>
553
559
  <div id="toolDialog">
554
560
  <div id="toolDialogHead">
@@ -53,6 +53,9 @@ const els = {
53
53
  dmColumnBtn: $('#dmColumnBtn'),
54
54
  dmMaxBtn: $('#dmMaxBtn'),
55
55
  dmBackBtn: $('#dmBackBtn'),
56
+ imgOverlay: $('#imgOverlay'),
57
+ imgOverlayImg: $('#imgOverlayImg'),
58
+ imgOverlayClose: $('#imgOverlayClose'),
56
59
  watchBtn: $('#watchBtn'),
57
60
  watchPaneList: $('#watchPaneList'),
58
61
  watchPaneOpen: $('#watchPaneOpen'),
@@ -5688,7 +5691,8 @@ if (els.dmOverlay) {
5688
5691
  els.dmOverlay.addEventListener('keydown', (e) => {
5689
5692
  if (e.key === 'Escape') {
5690
5693
  e.stopPropagation();
5691
- if (els.dmOverlay.classList.contains('dm-max')) toggleDmMax(false);
5694
+ if (els.imgOverlay && !els.imgOverlay.hidden) closeImgViewer(); /* önce görüntüleyici kapansın */
5695
+ else if (els.dmOverlay.classList.contains('dm-max')) toggleDmMax(false);
5692
5696
  else closeDmModal();
5693
5697
  }
5694
5698
  });
@@ -5702,6 +5706,47 @@ if (els.dmColumnBtn) {
5702
5706
  });
5703
5707
  }
5704
5708
 
5709
+ /* --- DM görsel görüntüleyici: görsele tıkla → TAM BOY (tıkla/Esc kapat) --- */
5710
+ function openImgViewer(src) {
5711
+ if (!els.imgOverlay || !els.imgOverlayImg || !src) return;
5712
+ els.imgOverlayImg.src = String(src);
5713
+ els.imgOverlay.hidden = false;
5714
+ }
5715
+
5716
+ function closeImgViewer() {
5717
+ if (els.imgOverlay) els.imgOverlay.hidden = true;
5718
+ if (els.imgOverlayImg) els.imgOverlayImg.src = '';
5719
+ }
5720
+
5721
+ if (els.imgOverlay) {
5722
+ els.imgOverlay.addEventListener('click', () => closeImgViewer());
5723
+ }
5724
+ if (els.imgOverlayClose) {
5725
+ els.imgOverlayClose.addEventListener('click', (e) => {
5726
+ e.stopPropagation();
5727
+ closeImgViewer();
5728
+ });
5729
+ }
5730
+ if (els.dmDialogList) {
5731
+ /* delegasyon: modal her mesajda yeniden çizilir — dinleyici tek yerde kalır */
5732
+ els.dmDialogList.addEventListener('click', (e) => {
5733
+ const img = e.target && e.target.closest ? e.target.closest('img.dmt-img') : null;
5734
+ if (!img || !img.src) return;
5735
+ e.stopPropagation();
5736
+ openImgViewer(img.src);
5737
+ });
5738
+ }
5739
+ document.addEventListener(
5740
+ 'keydown',
5741
+ (e) => {
5742
+ if (e.key !== 'Escape' || !els.imgOverlay || els.imgOverlay.hidden) return;
5743
+ e.preventDefault();
5744
+ e.stopPropagation();
5745
+ closeImgViewer();
5746
+ },
5747
+ true
5748
+ );
5749
+
5705
5750
  /* ---------------- TOOLS konsolu (kişisel araçlar) ----------------
5706
5751
  %APPDATA%\beast\tools\<ad>\tool.json + run.js — kullanıcı kendi araçlarını
5707
5752
  yazar; finance dahil TÜM oturumlar tool__<ad> olarak çağırır. Sol panelde
@@ -2331,6 +2331,7 @@ body.browser-open #settingsDialog { width: min(70vw, calc(100vw - var(--bw, 480p
2331
2331
  border-radius: 6px;
2332
2332
  margin-top: 5px;
2333
2333
  border: 1px solid var(--border, rgba(255,255,255,0.12));
2334
+ cursor: zoom-in;
2334
2335
  }
2335
2336
  .dmt-msg .dmt-img-note {
2336
2337
  font-size: 10px;
@@ -2338,6 +2339,40 @@ body.browser-open #settingsDialog { width: min(70vw, calc(100vw - var(--bw, 480p
2338
2339
  font-style: italic;
2339
2340
  margin-top: 3px;
2340
2341
  }
2342
+ /* DM görsel görüntüleyici: tam boy, tıkla/Esc ile kapanır */
2343
+ #imgOverlay {
2344
+ position: fixed;
2345
+ inset: 0;
2346
+ z-index: 400;
2347
+ background: rgba(0, 0, 0, 0.88);
2348
+ display: flex;
2349
+ align-items: center;
2350
+ justify-content: center;
2351
+ cursor: zoom-out;
2352
+ }
2353
+ #imgOverlay[hidden] { display: none; }
2354
+ #imgOverlay img {
2355
+ max-width: 96vw;
2356
+ max-height: 94vh;
2357
+ object-fit: contain;
2358
+ border-radius: 6px;
2359
+ box-shadow: 0 9px 40px rgba(0, 0, 0, 0.55);
2360
+ background: #0b0b0b;
2361
+ }
2362
+ #imgOverlayClose {
2363
+ position: absolute;
2364
+ top: 14px;
2365
+ right: 18px;
2366
+ width: 34px;
2367
+ height: 34px;
2368
+ border: none;
2369
+ border-radius: 8px;
2370
+ background: rgba(255, 255, 255, 0.12);
2371
+ color: #fff;
2372
+ font-size: 17px;
2373
+ cursor: pointer;
2374
+ }
2375
+ #imgOverlayClose:hover { background: rgba(255, 255, 255, 0.26); }
2341
2376
  /* ajan adının yanındaki KOD rozeti (oturum id) */
2342
2377
  .dm-code {
2343
2378
  font-size: 9px; font-weight: 700; letter-spacing: 0.5px;