beast-agent 2.20.0 → 2.21.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.20.0",
4
+ "version": "2.21.0",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
@@ -5317,23 +5317,25 @@ function emitSafe(engine, sessionId, ev) {
5317
5317
 
5318
5318
  /* AJAN DM aracı: paralel/finance ajanlarının birbirine kısa mesaj atması.
5319
5319
  Salt metin — hedef ajanın oturumuna [AJAN DM] bloğu olarak düşer; AJAN DM
5320
- panelinde iki yönlü görünür. */
5320
+ panelinde iki yönlü görünür. topic verirsen aynı konudaki mesajlar aynı
5321
+ oturumda toplanır (cevap zinciri tek thread'de devam eder). */
5321
5322
  const AGENT_DM_DEF = {
5322
5323
  type: 'function',
5323
5324
  function: {
5324
5325
  name: 'agent_dm',
5325
5326
  description:
5326
- 'Send a short DM to another RUNNING agent (parallel agents / finance agents) to coordinate: share findings, ask status, warn about risk, hand off work. `to` = target session id OR a keyword from the agent title (e.g. "GOLD", "Trader"). The message is delivered to the other agent and shown in the Agent DM panel.',
5327
+ 'Send a short DM to another running agent (parallel agents / finance agents) to coordinate: share findings, ask status, warn about risk, hand off work. `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.',
5327
5328
  parameters: {
5328
5329
  type: 'object',
5329
5330
  properties: {
5330
5331
  to: { type: 'string', description: 'Target agent session id or a title keyword' },
5332
+ topic: { type: 'string', description: 'Short subject label for the conversation thread, e.g. "GOLD pozisyon riski"' },
5331
5333
  message: { type: 'string', description: 'Short message (1-3 sentences)' },
5332
5334
  },
5333
5335
  required: ['to', 'message'],
5334
5336
  },
5335
5337
  },
5336
- };
5338
+ };
5337
5339
 
5338
5340
  const TOOLS = [
5339
5341
  ...tools.definitions,
@@ -6150,12 +6152,15 @@ Engine.prototype._agentDmSend = function (fromSid, args) {
6150
6152
  if (String(target) === String(fromSid)) return { ok: false, error: 'kendine DM atılamaz' };
6151
6153
  const fromJob = jobs.get(String(fromSid));
6152
6154
  const toJob = jobs.get(target);
6155
+ /* konu etiketi: aynı konudaki DM'ler panelde AYNI oturumda toplanır */
6156
+ const topic = String((args && args.topic) || '').replace(/\s+/g, ' ').trim().slice(0, 60) || '(genel)';
6153
6157
  const dm = {
6154
6158
  at: new Date().toISOString(),
6155
6159
  from: String(fromSid),
6156
6160
  fromTitle: (fromJob && fromJob.title) || 'Ajan',
6157
6161
  to: String(target),
6158
6162
  toTitle: (toJob && toJob.title) || 'Ajan',
6163
+ topic,
6159
6164
  text,
6160
6165
  };
6161
6166
  this._agentDms = this._agentDms || [];
@@ -6165,15 +6170,16 @@ Engine.prototype._agentDmSend = function (fromSid, args) {
6165
6170
  fs.writeFileSync(this._agentDmsFile, JSON.stringify({ dms: this._agentDms }, null, 2));
6166
6171
  } catch {}
6167
6172
  emitSafe(this, target, { type: 'agent-dm', ...dm });
6168
- /* teslim: hedef meşgulse pending kuyruğunda bekler, done olunca düşer */
6173
+ /* teslim: hedef meşgulse pending kuyruğunda bekler, done olunca düşer.
6174
+ Cevap zinciri AYNI thread'den sürsün diye topic geri bildirilir. */
6169
6175
  (this._pendingReports = this._pendingReports || []).push({
6170
6176
  parentId: target,
6171
6177
  text:
6172
- `[AJAN DM — ${dm.fromTitle}]\n${text}\n` +
6173
- `(Cevabını agent_dm aracıyla ver — to: "${dm.fromTitle}")`,
6178
+ `[AJAN DM — ${dm.fromTitle} · konu: "${topic}"]\n${text}\n` +
6179
+ `(Cevabını agent_dm aracıyla ver — to: "${dm.fromTitle}", topic: "${topic}")`,
6174
6180
  });
6175
6181
  this.flushPendingReports(target);
6176
- return { ok: true, to: target, toTitle: dm.toTitle };
6182
+ return { ok: true, to: target, toTitle: dm.toTitle, topic };
6177
6183
  } catch (e) {
6178
6184
  return { ok: false, error: String((e && e.message) || e) };
6179
6185
  }
@@ -129,7 +129,8 @@ Get-Content "$env:APPDATA\\beast\\sessions\\bg-jobs.json" | ConvertFrom-Json | %
129
129
  | \`agent/agentdefs.js\` | Özel ajan tanımları: %APPDATA%\beast\agents\\*.md (frontmatter: model/tools/steps/mode + prompt gövdesi); /agent ile bağlanır, run_background(agent:) ile paralel ajana verilir |
130
130
  | \`agent/searxng.js\` | SearXNG entegrasyonu: gömülü Python'a kurulum + 127.0.0.1:8888 başlatıcı + JSON arama motoru |
131
131
  | \`agent/bots.js\` | Bot CRUD, 5 haneli bot kodu, izolasyon; ad-ilk-harf doğrulaması |
132
- | \`agent/skills.js\` | Builtin tohumlar (help, email, python-web-search, pdf, gold-trading + superpowers metodoloji paketi: brainstorming/writing-plans/executing-plans/TDD/paralel-ajan/alt-ajan-geliştirme/sistematik-debugging/doğrulama/skill-yazma) + SKILL.md taraması + taslaklar |
132
+ | \`agent/skills.js\` | Builtin tohumlar (help, email, python-web-search, pdf, gold-trading, tool-yazma + superpowers metodoloji paketi: brainstorming/writing-plans/executing-plans/TDD/paralel-ajan/alt-ajan-geliştirme/sistematik-debugging/doğrulama/skill-yazma) + SKILL.md taraması + taslaklar |
133
+ | \`agent/customtools.js\` | Kişisel toollar: %APPDATA%\\beast\\tools\\<ad>\\tool.json + run.js (stdin JSON → stdout JSON) — tool__<ad> olarak TÜM oturumlara açılır; sol panel TOOLLAR kutusunda yazılır/test edilir. Yeni tool yazarken \`tool-yazma\` skill'ini oku. |
133
134
  | \`agent/memory.js\`, \`agent/kb.js\` | Kalıcı hafıza + bilgi bankası |
134
135
  | \`cron.js\` (src kökü), \`agent/watchers.js\`, \`agent/bus.js\` | Cron, izleyici, olay merkezi — /stop bunlara DOKUNMAZ |
135
136
  | \`agent/usage.js\` | Token/maliyet sayaçları |
@@ -456,6 +457,87 @@ const { createWorker } = require('tesseract.js');
456
457
  | read_file boş metin döndü | taranmış PDF → 4. adım (görselleştir) + 5. adım (OCR) |
457
458
  | tesseract dil verisi hatası | ilk indirme internet ister; tekrar dene |`,
458
459
  },
460
+ {
461
+ folder: 'tool-yazma',
462
+ force: true,
463
+ body: `---
464
+ name: tool-yazma
465
+ description: Kişisel TOOL yazma/düzenleme rehberi — %APPDATA%\\beast\\tools\\<ad>\\ altına tool.json + run.js yazılacak veya mevcut bir tool editlenecekse ÖNCE bu skill'i oku. Tool'un sözleşmesi: stdin JSON argüman → stdout JSON sonuç; ajan tool__<ad> olarak çağırır (Beast Finance dahil tüm oturumlarda çalışır).
466
+ version: 1.0.0
467
+ ---
468
+
469
+ # Kişisel TOOL Yazma
470
+
471
+ ## TOOL NEDİR / NE ZAMAN YAZILIR
472
+
473
+ Kişisel tool = modelin alet çantasına GİREN gerçek bir araçtır: %APPDATA%\\beast\\tools\\<slug>\\ klasöründe **tool.json** (tanım) + **run.js** (kod) bulunur. Tüm oturumlarda — Beast Finance chat/trader/işçiler dahil — \`tool__<slug>\` adıyla çağrılır.
474
+
475
+ Yaz ZAMAN: mevcut araçlar işi görmüyorsa (örn. broker sembol adlandırması mt5_trade'i düşürüyorsa), özel bir API'ye bağlanman, tekrarlı bir hesap/işlem rutini gerekiyorsa. Basit işler için YAZMA — önce yerleşik araçları (mt5_*, web_search, python_run, run_command) dene.
476
+
477
+ ## SÖZLEŞME (birebir uygula)
478
+
479
+ - **stdin**: JSON argümanlar (tek satırda, güvenli parse)
480
+ - **stdout**: SADECE JSON sonuç — son satır JSON'a çevrilmeye çalışılır; günlük/metin yazarsan JSON bozulur, \`console.error\` kullan
481
+ - **çalışma klasörü**: tool'un kendi klasörü (yanına dosya yazabilirsin)
482
+ - **zaman aşımı**: 90 sn — uzun işleri kendin parçalama, \`run_background\`'a devret
483
+ - **sonuç biçimi**: \`{ "ok": true, ... }\` ya da \`{ "ok": false, "error": "sebep" }\` — ok alanı ZORUNLU
484
+
485
+ ## YENİ TOOL OLUŞTURMA (2 dosya, write_file ile)
486
+
487
+ \`%APPDATA%\\beast\\tools\\<slug>\\tool.json\`:
488
+ \`\`\`json
489
+ {
490
+ "name": "gold_emir",
491
+ "description": "GOLD için özel emir açar — side (buy/sell), lot, sl, tp alır; sonucu ticket ile döndürür.",
492
+ "parameters": {
493
+ "type": "object",
494
+ "properties": {
495
+ "side": { "type": "string", "description": "buy | sell" },
496
+ "lot": { "type": "number", "description": "0.01-1.0" },
497
+ "sl": { "type": "number" },
498
+ "tp": { "type": "number" }
499
+ },
500
+ "required": ["side", "lot"]
501
+ },
502
+ "updatedAt": "2026-09-09T00:00:00.000Z"
503
+ }
504
+ \`\`\`
505
+
506
+ \`%APPDATA%\\beast\\tools\\<slug>\\run.js\`:
507
+ \`\`\`javascript
508
+ 'use strict';
509
+ // stdin: JSON args · stdout: JSON sonuç (SADECE JSON yaz!)
510
+ const fs = require('fs');
511
+ let args = {};
512
+ try { args = JSON.parse(fs.readFileSync(0, 'utf8') || '{}'); } catch {}
513
+ (function main() {
514
+ const { side, lot, sl, tp } = args;
515
+ if (!side || !lot) {
516
+ console.log(JSON.stringify({ ok: false, error: 'side ve lot zorunlu' }));
517
+ return;
518
+ }
519
+ // --- kendi işin burada: HTTP çağrısı, MT5 köprüsü, dosya, hesap... ---
520
+ // Örn: https modülüyle özel endpoint'e istek at, sonucu ok alanıyla sar.
521
+ console.log(JSON.stringify({ ok: true, side, lot, sl: sl || 0, tp: tp || 0, ticket: 0 }));
522
+ })();
523
+ \`\`\`
524
+
525
+ Slug kuralları: a-z, 0-9, alt çizgi, tire — 2-24 karakter (örn: gold_emir, haber_ozet). Hazır taslak: %APPDATA%\\beast\\tools\\ornek_echo klasörünü referans al; yeni tool'u o biçimle yaz.
526
+
527
+ ## DÜZENLEME / DOĞRULAMA
528
+
529
+ 1. Mevcut toolu değiştirme: \`edit_file\` ile tool.json / run.js düzenle — ASLA tüm klasörü silip baştan yazma.
530
+ 2. Kayıttan sonra tool ANINDA aktifleşir (yeniden başlatma gerekmez) — bir sonraki turda \`tool__<slug>\` çağrılabilir.
531
+ 3. Doğrula: \`run_command\` ile \`echo \\"{\\\\\\"mesaj\\\\\\":\\\\\\"test\\\\\\"}\\" | node \\"%APPDATA%\\\\beast\\\\tools\\\\<slug>\\\\run.js\\"\` — çıktı JSON olmalı ve \`ok\` alanı içermeli.
532
+ 4. Hata çıktısı \`ok:false + error\` olsun — model hatayı okuyup düzeltir; çıplak stack yasak.
533
+
534
+ ## GÜVENLİK + STİL
535
+
536
+ - Şifre/API anahtarını ASLA stdout'a, log'a, skill'e yazma.
537
+ - Ağ çağrılarında timeout kullan (\`https\` modülü + \`req.setTimeout\` veya fetch); sonsuz bekleyen script yazma.
538
+ - Tek sorumluluk: bir tool = bir iş. Çok işli dev tool yazma — parçala.
539
+ - Kullanıcıya tool'u nasıl yöneteceğini söyle: sol panelde TOOLLAR kutusunda test/düzenle/sil + 📂 klasör.`,
540
+ },
459
541
  ];
460
542
 
461
543
  function writeSeed(seed) {
@@ -373,6 +373,21 @@
373
373
  </aside>
374
374
  </div>
375
375
 
376
+ <div id="dmOverlay" hidden>
377
+ <div id="dmDialog">
378
+ <div id="dmDialogHead">
379
+ <span id="dmDialogTitle">AJAN DM — OTURUMLAR</span>
380
+ <span class="bot-bar-btns">
381
+ <button id="dmColumnBtn" title="Yan sütunda canlı akış olarak da göster">&#x25A4;&#xFE0E;</button>
382
+ <button id="dmModalClear" title="Tüm DM geçmişini sil"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M8 6V4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v2"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/></svg></button>
383
+ <button id="dmModalClose" title="Kapat">&#x2715;&#xFE0E;</button>
384
+ </span>
385
+ </div>
386
+ <div id="dmDialogList"></div>
387
+ <div class="dm-hint">Thread = ajan çifti + konu · gelen/giden hepsi kronolojik</div>
388
+ </div>
389
+ </div>
390
+
376
391
  <div id="toolOverlay" hidden>
377
392
  <div id="toolDialog">
378
393
  <div id="toolDialogHead">
@@ -45,6 +45,11 @@ const els = {
45
45
  dmBtn: $('#dmBtn'),
46
46
  dmRailList: $('#dmRailList'),
47
47
  dmClear: $('#dmClear'),
48
+ dmOverlay: $('#dmOverlay'),
49
+ dmDialogList: $('#dmDialogList'),
50
+ dmModalClose: $('#dmModalClose'),
51
+ dmModalClear: $('#dmModalClear'),
52
+ dmColumnBtn: $('#dmColumnBtn'),
48
53
  watchBtn: $('#watchBtn'),
49
54
  watchPaneList: $('#watchPaneList'),
50
55
  watchPaneOpen: $('#watchPaneOpen'),
@@ -5019,25 +5024,41 @@ async function loadRailChat(id) {
5019
5024
  }
5020
5025
  }
5021
5026
 
5022
- /* ---------------- AJAN DM sütunu (ajanlar arası konuşma) ----------------
5023
- rail ile aynı mimari: sağda ikinci sütun, dmBtn ile açılır/kapanır.
5024
- DM'ler ajanların agent_dm araç çağrılarından gelir; çiftlere gruplanır. */
5027
+ /* ---------------- AJAN DM (ajanlar arası konuşma) ----------------
5028
+ İki görünüm: ✉ modal (oturum listesi) + yan sütun (canlı akış).
5029
+ Thread anahtarı = ajan çifti + KONU — aynı konudaki mesajlar tek
5030
+ oturumda toplanır, gelen/giden hepsi kronolojik görünür. */
5025
5031
 
5026
5032
  const dmState = {
5027
- dms: [], // { at, from, fromTitle, to, toTitle, text }
5028
- open: new Set(), // açık thread key'leri
5033
+ dms: [], // { at, from, fromTitle, to, toTitle, topic, text }
5034
+ open: new Set(), // açık thread key'leri (modal + sütun ortak)
5029
5035
  };
5030
5036
 
5031
- function dmPairOf(dm) {
5037
+ function dmThreadOf(dm) {
5032
5038
  const a = String(dm.from || '');
5033
5039
  const b = String(dm.to || '');
5034
- return a < b ? a + '|' + b : b + '|' + a;
5040
+ const pair = a < b ? a + '|' + b : b + '|' + a;
5041
+ return pair + '|' + String(dm.topic || '(genel)').toLowerCase();
5035
5042
  }
5036
5043
 
5037
- function dmPairTitle(dm) {
5044
+ function dmThreadTitle(dm) {
5038
5045
  const a = String(dm.fromTitle || 'Ajan');
5039
5046
  const b = String(dm.toTitle || 'Ajan');
5040
- return a < b ? a + ' ↔ ' + b : b + ' ↔ ' + a;
5047
+ const pair = a < b ? a + ' ↔ ' + b : b + ' ↔ ' + a;
5048
+ const topic = String(dm.topic || '(genel)');
5049
+ return topic === '(genel)' ? pair : pair + ' · ' + topic;
5050
+ }
5051
+
5052
+ function dmThreads() {
5053
+ const threads = new Map();
5054
+ for (const dm of dmState.dms) {
5055
+ const key = dmThreadOf(dm);
5056
+ if (!threads.has(key)) threads.set(key, []);
5057
+ threads.get(key).push(dm);
5058
+ }
5059
+ return [...threads.entries()].sort(
5060
+ (x, y) => new Date(y[1][y[1].length - 1].at) - new Date(x[1][x[1].length - 1].at)
5061
+ );
5041
5062
  }
5042
5063
 
5043
5064
  function dmTime(iso) {
@@ -5046,6 +5067,15 @@ function dmTime(iso) {
5046
5067
  return d.toLocaleTimeString('tr-TR', { hour: '2-digit', minute: '2-digit' });
5047
5068
  }
5048
5069
 
5070
+ function dmMsgHtml(m) {
5071
+ return (
5072
+ `<div class="dmt-msg"><span class="dm-who">${escapeHtml(String(m.fromTitle || 'Ajan'))} → ${escapeHtml(String(m.toTitle || 'Ajan'))}</span>` +
5073
+ `${escapeHtml(String(m.text || ''))}<span class="dm-at">${dmTime(m.at)}</span></div>`
5074
+ );
5075
+ }
5076
+
5077
+ /* --- yan sütun --- */
5078
+
5049
5079
  function renderDmRail() {
5050
5080
  const list = els.dmRailList;
5051
5081
  if (!list) return;
@@ -5053,37 +5083,19 @@ function renderDmRail() {
5053
5083
  list.innerHTML = '<div class="dm-empty">Ajanlar arasında DM yok.<br>Ajanlar agent_dm aracıyla birbirine mesaj attıkça burada görünür.</div>';
5054
5084
  return;
5055
5085
  }
5056
- /* çiftlere grupla (kronolojik) — en yeni thread üstte */
5057
- const threads = new Map();
5058
- for (const dm of dmState.dms) {
5059
- const key = dmPairOf(dm);
5060
- if (!threads.has(key)) threads.set(key, []);
5061
- threads.get(key).push(dm);
5062
- }
5063
- const rows = [...threads.entries()].sort(
5064
- (x, y) => new Date(y[1][y[1].length - 1].at) - new Date(x[1][x[1].length - 1].at)
5065
- );
5066
5086
  list.innerHTML = '';
5067
- for (const [key, msgs] of rows) {
5087
+ for (const [key, msgs] of dmThreads()) {
5068
5088
  const open = dmState.open.has(key);
5069
5089
  const last = msgs[msgs.length - 1];
5070
5090
  const el = document.createElement('div');
5071
5091
  el.className = 'dmt' + (open ? ' open' : '');
5072
5092
  el.innerHTML =
5073
5093
  '<div class="dmt-head">' +
5074
- `<span class="dmt-title">${escapeHtml(dmPairTitle(last))}</span>` +
5094
+ `<span class="dmt-title">${escapeHtml(dmThreadTitle(last))}</span>` +
5075
5095
  `<span class="dmt-count">${msgs.length}</span>` +
5076
5096
  '</div>' +
5077
5097
  `<div class="dmt-last">${escapeHtml(String(last.text || '').replace(/\s+/g, ' ').slice(0, 90))} · ${dmTime(last.at)}</div>` +
5078
- '<div class="dmt-msgs">' +
5079
- msgs
5080
- .map(
5081
- (m) =>
5082
- `<div class="dmt-msg"><span class="dm-who">${escapeHtml(String(m.fromTitle || 'Ajan'))} → ${escapeHtml(String(m.toTitle || 'Ajan'))}</span>` +
5083
- `${escapeHtml(String(m.text || ''))}<span class="dm-at">${dmTime(m.at)}</span></div>`
5084
- )
5085
- .join('') +
5086
- '</div>';
5098
+ '<div class="dmt-msgs">' + msgs.map(dmMsgHtml).join('') + '</div>';
5087
5099
  el.addEventListener('click', () => {
5088
5100
  if (dmState.open.has(key)) dmState.open.delete(key);
5089
5101
  else dmState.open.add(key);
@@ -5111,18 +5123,77 @@ function toggleDmRail(hide) {
5111
5123
  if (!hide) refreshDmRail();
5112
5124
  }
5113
5125
 
5114
- if (els.dmBtn) {
5115
- els.dmBtn.addEventListener('click', () => {
5116
- toggleDmRail(!$('#dmRail').classList.contains('dm-hidden'));
5126
+ /* --- modal (✉ butonu) --- */
5127
+
5128
+ function renderDmModal() {
5129
+ const list = els.dmDialogList;
5130
+ if (!list) return;
5131
+ if (!dmState.dms.length) {
5132
+ list.innerHTML = '<div class="dm-empty">Henüz ajan DM\u2019i yok.<br>Ajanlar agent_dm aracıyla birbirine mesaj attıkça oturumlar burada birikir.</div>';
5133
+ return;
5134
+ }
5135
+ list.innerHTML = '';
5136
+ for (const [key, msgs] of dmThreads()) {
5137
+ const open = dmState.open.has(key);
5138
+ const last = msgs[msgs.length - 1];
5139
+ const el = document.createElement('div');
5140
+ el.className = 'dmt' + (open ? ' open' : '');
5141
+ el.innerHTML =
5142
+ '<div class="dmt-head">' +
5143
+ `<span class="dmt-title">${escapeHtml(dmThreadTitle(last))}</span>` +
5144
+ `<span class="dmt-count">${msgs.length}</span>` +
5145
+ '</div>' +
5146
+ `<div class="dmt-last">${escapeHtml(String(last.text || '').replace(/\s+/g, ' ').slice(0, 120))} · ${dmTime(last.at)}</div>` +
5147
+ '<div class="dmt-msgs">' + msgs.map(dmMsgHtml).join('') + '</div>';
5148
+ el.addEventListener('click', () => {
5149
+ if (dmState.open.has(key)) dmState.open.delete(key);
5150
+ else dmState.open.add(key);
5151
+ renderDmModal();
5152
+ const again = els.dmDialogList.querySelector('.dmt.open .dmt-msgs');
5153
+ if (again) again.scrollTop = again.scrollHeight;
5154
+ });
5155
+ list.appendChild(el);
5156
+ }
5157
+ }
5158
+
5159
+ async function openDmModal() {
5160
+ if (!els.dmOverlay) return;
5161
+ els.dmOverlay.hidden = false;
5162
+ await refreshDmRail(); // kayıtları tazele
5163
+ renderDmModal();
5164
+ if (els.dmBtn) els.dmBtn.classList.add('on');
5165
+ }
5166
+
5167
+ function closeDmModal() {
5168
+ if (els.dmOverlay) els.dmOverlay.hidden = true;
5169
+ if (els.dmBtn) els.dmBtn.classList.remove('on');
5170
+ }
5171
+
5172
+ async function dmClearAll() {
5173
+ if (!(await uiConfirm('TÜM ajan DM geçmişi silinsin mi?', 'Sil'))) return;
5174
+ try { await beast.agentDmsClear(); } catch {}
5175
+ dmState.dms = [];
5176
+ renderDmRail();
5177
+ renderDmModal();
5178
+ toast('Ajan DM geçmişi silindi');
5179
+ }
5180
+
5181
+ if (els.dmBtn) els.dmBtn.addEventListener('click', openDmModal);
5182
+ if (els.dmModalClose) els.dmModalClose.addEventListener('click', closeDmModal);
5183
+ if (els.dmModalClear) els.dmModalClear.addEventListener('click', dmClearAll);
5184
+ if (els.dmOverlay) {
5185
+ els.dmOverlay.addEventListener('click', (e) => {
5186
+ if (e.target === els.dmOverlay) closeDmModal();
5187
+ });
5188
+ els.dmOverlay.addEventListener('keydown', (e) => {
5189
+ if (e.key === 'Escape') { e.stopPropagation(); closeDmModal(); }
5117
5190
  });
5118
5191
  }
5119
- if (els.dmClear) {
5120
- els.dmClear.addEventListener('click', async () => {
5121
- if (!(await uiConfirm('TÜM ajan DM geçmişi silinsin mi?', 'Sil'))) return;
5122
- try { await beast.agentDmsClear(); } catch {}
5123
- dmState.dms = [];
5124
- renderDmRail();
5125
- toast('Ajan DM geçmişi silindi');
5192
+ if (els.dmClear) els.dmClear.addEventListener('click', dmClearAll);
5193
+ if (els.dmColumnBtn) {
5194
+ els.dmColumnBtn.addEventListener('click', () => {
5195
+ closeDmModal();
5196
+ toggleDmRail(false); // yan sütunda canlı akış
5126
5197
  });
5127
5198
  }
5128
5199
 
@@ -5456,7 +5527,7 @@ function onEvent(ev) {
5456
5527
  scheduleAgentsRender();
5457
5528
  return;
5458
5529
  }
5459
- /* AJAN DM: ajanlar arası mesaj — panel açıksa canlı düşer */
5530
+ /* AJAN DM: ajanlar arası mesaj — açık görünümde canlı düşer (modal/sütun) */
5460
5531
  if (ev.type === 'agent-dm') {
5461
5532
  dmState.dms.push({
5462
5533
  at: ev.at,
@@ -5464,9 +5535,11 @@ function onEvent(ev) {
5464
5535
  fromTitle: ev.fromTitle,
5465
5536
  to: ev.to,
5466
5537
  toTitle: ev.toTitle,
5538
+ topic: ev.topic,
5467
5539
  text: ev.text,
5468
5540
  });
5469
5541
  if (dmState.dms.length > 400) dmState.dms.splice(0, dmState.dms.length - 400);
5542
+ if (els.dmOverlay && !els.dmOverlay.hidden) renderDmModal();
5470
5543
  if ($('#dmRail') && !$('#dmRail').classList.contains('dm-hidden')) renderDmRail();
5471
5544
  return;
5472
5545
  }
@@ -110,15 +110,16 @@ body {
110
110
  }
111
111
  #newChat:hover { background: rgba(19, 19, 21, 0.14); }
112
112
 
113
- #sessList { flex: 1; overflow-y: auto; padding: 4px 8px; }
113
+ #sessList { flex: 1 1 0; min-height: 0; overflow-y: auto; padding: 4px 8px; }
114
114
 
115
115
  /* ---------- İZLEYİCİLER paneli (sol kenar çubuğu — sohbet geçmişinin alt yarısı) ----------
116
- Yalnız agent (chat) modunda görünür; IDE/Studio/Sandbox'ta sessList ile birlikte gizli. */
116
+ Yalnız agent (chat) modunda görünür; IDE/Studio/Sandbox'ta sessList ile birlikte gizli.
117
+ Sohbet geçmişi + izleyiciler + toollar EŞİT yer paylaşır (flex 1 1 0). */
117
118
  #watchPane {
118
119
  display: none;
119
120
  flex-direction: column;
120
- height: 36%;
121
- min-height: 120px;
121
+ flex: 1 1 0;
122
+ min-height: 110px;
122
123
  border-top: 1px solid var(--border);
123
124
  background: var(--panel);
124
125
  }
@@ -2187,6 +2188,66 @@ body.rail-hidden #dmRailHead { padding-right: 148px; }
2187
2188
  margin-top: 2px;
2188
2189
  }
2189
2190
 
2191
+ /* ---------- AJAN DM modalı (✉) ---------- */
2192
+ #dmOverlay {
2193
+ position: fixed;
2194
+ inset: 0;
2195
+ background: rgba(0, 0, 0, 0.45);
2196
+ z-index: 300;
2197
+ display: flex;
2198
+ align-items: center;
2199
+ justify-content: center;
2200
+ }
2201
+ #dmOverlay[hidden] { display: none; }
2202
+ #dmDialog {
2203
+ width: min(680px, 92vw);
2204
+ max-height: 86vh;
2205
+ display: flex;
2206
+ flex-direction: column;
2207
+ background: var(--panel);
2208
+ border: 1px solid var(--border);
2209
+ border-radius: 12px;
2210
+ padding: 14px 16px;
2211
+ gap: 10px;
2212
+ }
2213
+ #dmDialogHead {
2214
+ display: flex;
2215
+ align-items: center;
2216
+ justify-content: space-between;
2217
+ font-size: 13px;
2218
+ font-weight: 800;
2219
+ letter-spacing: 1px;
2220
+ color: var(--text);
2221
+ }
2222
+ #dmDialogHead button {
2223
+ border: none;
2224
+ background: none;
2225
+ color: var(--muted);
2226
+ cursor: pointer;
2227
+ font-size: 13px;
2228
+ padding: 2px 6px;
2229
+ border-radius: 6px;
2230
+ display: inline-flex;
2231
+ align-items: center;
2232
+ }
2233
+ #dmDialogHead button:hover { color: var(--accent); background: var(--panel2); }
2234
+ #dmDialogHead .bot-bar-btns { display: flex; gap: 4px; }
2235
+ #dmDialogList {
2236
+ flex: 1 1 auto;
2237
+ min-height: 200px;
2238
+ overflow-y: auto;
2239
+ display: flex;
2240
+ flex-direction: column;
2241
+ align-items: stretch;
2242
+ }
2243
+ .dm-hint {
2244
+ flex: none;
2245
+ font-size: 10px;
2246
+ color: var(--muted);
2247
+ text-align: center;
2248
+ letter-spacing: 0.4px;
2249
+ }
2250
+
2190
2251
  /* PİYASA satırındaki ▶ ajan butonu */
2191
2252
  .fin-sym { position: relative; }
2192
2253
  .fin-sym .fs-agent {
@@ -2275,10 +2336,11 @@ body.rail-hidden #dmRailHead { padding-right: 148px; }
2275
2336
  color: var(--muted);
2276
2337
  }
2277
2338
 
2278
- /* ---------- sol panel: TOOLS konsolu (kişisel araçlar) ---------- */
2339
+ /* ---------- sol panel: TOOLS konsolu (kişisel araçlar) ----------
2340
+ Sohbet geçmişi + izleyiciler + toollar EŞİT yer paylaşır (flex 1 1 0). */
2279
2341
  #toolBox {
2280
- flex: none;
2281
- max-height: 32%;
2342
+ flex: 1 1 0;
2343
+ min-height: 90px;
2282
2344
  display: none;
2283
2345
  flex-direction: column;
2284
2346
  border-top: 1px solid var(--border);
@@ -2296,8 +2358,8 @@ body:not(.ide-mode):not(.studio-mode):not(.sandbox-mode) #toolBox { display: fle
2296
2358
  color: var(--muted);
2297
2359
  }
2298
2360
  #toolList {
2361
+ flex: 1 1 auto;
2299
2362
  min-height: 40px;
2300
- max-height: 200px;
2301
2363
  overflow-y: auto;
2302
2364
  display: flex;
2303
2365
  flex-direction: column;