beast-agent 2.45.0 → 2.46.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.45.0",
4
+ "version": "2.46.0",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
@@ -1428,10 +1428,10 @@ class Engine {
1428
1428
  return out.slice(0, 100);
1429
1429
  }
1430
1430
 
1431
- /* Beast Code panel oturumları: bgTitle 'Beast Code' olan gizli oturumlar.
1432
- Soldaki dosya panelinin sohbet geçmişi listesi bunları gösterir —
1433
- ana sohbet listesinden (listSessions) TAMAMEN AYRIDIR. */
1434
- listBcSessions(limit = 100) {
1431
+ /* Panel oturumları: belirli bir bgTitle taşıyan gizli oturumlar (Beast Code,
1432
+ Beast MCP…) — ana sohbet listesinden (listSessions) TAMAMEN AYRIDIR. */
1433
+ _listTitledSessions(title, limit = 100) {
1434
+ const wanted = String(title || '');
1435
1435
  let files = [];
1436
1436
  try {
1437
1437
  files = fs.readdirSync(this.sessionsDir).filter((f) => f.endsWith('.jsonl'));
@@ -1441,7 +1441,7 @@ class Engine {
1441
1441
  const id = f.replace(/\.jsonl$/, '');
1442
1442
  let s;
1443
1443
  try { s = this._load(id); } catch { continue; }
1444
- if (s.bgTitle !== 'Beast Code') continue; // yalnız Beast Code oturumları
1444
+ if (s.bgTitle !== wanted) continue;
1445
1445
  let title = '';
1446
1446
  for (const m of s.messages) {
1447
1447
  if (m.role !== 'user') continue;
@@ -1453,7 +1453,7 @@ class Engine {
1453
1453
  }
1454
1454
  out.push({
1455
1455
  id,
1456
- title: title || 'Beast Code oturumu',
1456
+ title: title || wanted + ' oturumu',
1457
1457
  updatedAt: s.updatedAt,
1458
1458
  createdAt: s.createdAt,
1459
1459
  count: s.messages.length,
@@ -1464,6 +1464,16 @@ class Engine {
1464
1464
  return out.slice(0, Math.max(1, Math.min(200, Number(limit) || 100)));
1465
1465
  }
1466
1466
 
1467
+ /* Beast Code panel oturumları: soldaki dosya panelinin geçmiş listesi gösterir */
1468
+ listBcSessions(limit = 100) {
1469
+ return this._listTitledSessions('Beast Code', limit);
1470
+ }
1471
+
1472
+ /* Beast MCP sohbet oturumları: MCP modundaki sağ sohbet paneli gösterir */
1473
+ listMcpSessions(limit = 100) {
1474
+ return this._listTitledSessions('Beast MCP', limit);
1475
+ }
1476
+
1467
1477
  createSession() {
1468
1478
  const id = uid();
1469
1479
  const code = this._newCode();
package/src/main.js CHANGED
@@ -3311,6 +3311,7 @@ function reloadBackend() {
3311
3311
  bcFlushOnDone(ev); /* Beast Code kuyruğunu iş bitiminde boşalt */
3312
3312
  stFlushOnDone(ev); /* Beast Studio kuyruğunu iş bitiminde boşalt */
3313
3313
  sbFlushOnDone(ev); /* Sandbox kuyruğunu iş bitiminde boşalt */
3314
+ mcpChatFlushOnDone(ev); /* MCP sohbet kuyruğunu iş bitiminde boşalt */
3314
3315
  finFlushOnDone(ev); /* Finance trader döngüsü: iş bitince sıradaki turu planla */
3315
3316
  /* BC canlı önizleme: ajan bir dev server başlattıysa adresi yakala —
3316
3317
  preview butonu ve otomatik açılış DAİMA bu sunucuyu öncelikli kullanır */
@@ -6635,6 +6636,211 @@ ipcMain.handle('mcp:refresh', async (_e, name) => {
6635
6636
  }
6636
6637
  });
6637
6638
 
6639
+ /* ---------------- BEAST MCP SOHBETİ (MCP modu SAĞ panel) ----------------
6640
+ MCP moduna özel, ANA SOHBETTEN TAMAMEN AYRI oturum: bgTitle 'Beast MCP'
6641
+ (listSessions gizler, listMcpSessions listeler; geçmiş seçici eski
6642
+ oturumları açabilir). MCP sunucu araçları zaten tüm engine turlarına
6643
+ enjekte edilir; panel yalnız oturum yaşam döngüsünü yönetir.
6644
+ Gönderim main chat disiplininin aynısı: boşta kısa debounce (ard arda
6645
+ mesajlar birleşir), meşgulken steer (koşan tura anında eklenir). */
6646
+ let mcpChatSid = ''; /* aktif MCP sohbet oturumu */
6647
+ let mcpChatFresh = false; /* 'Yeni' istendi: eski sohbete DÖNME, taze aç */
6648
+ const mcpChatQueue = new Map(); /* 'mcp' → { timer, msgs[] } */
6649
+ const MCP_CHAT_DEBOUNCE_MS = 900;
6650
+
6651
+ function mcpChatCreate() {
6652
+ const s = engine._load(engine.createSession().id);
6653
+ s.messages = s.messages || [];
6654
+ s.bgTitle = 'Beast MCP'; /* _view.isBg → ana sohbet geçmişi listesinde gizli */
6655
+ try {
6656
+ fs.appendFileSync(
6657
+ engine._file(s.id),
6658
+ JSON.stringify({ t: 'meta2', bgOf: '', title: 'Beast MCP', at: new Date().toISOString() }) + '\n'
6659
+ );
6660
+ } catch {}
6661
+ engine.cache.set(s.id, s);
6662
+ mcpChatSid = s.id;
6663
+ mcpChatFresh = false;
6664
+ return s;
6665
+ }
6666
+
6667
+ function mcpChatSession() {
6668
+ if (mcpChatSid) {
6669
+ try {
6670
+ const s = engine.cache.get(mcpChatSid) || engine._load(mcpChatSid);
6671
+ if (s && s.bgTitle === 'Beast MCP') return s;
6672
+ } catch {}
6673
+ mcpChatSid = '';
6674
+ }
6675
+ /* 'Yeni' istenmediyse son MCP sohbetine dön: uygulama yeniden açıldığında
6676
+ sohbet kaldığı yerden sürer (geçmiş seçiciden eski oturumlar açılabilir) */
6677
+ if (!mcpChatFresh) {
6678
+ try {
6679
+ const last = engine.listMcpSessions(50).find((it) => it.count > 0);
6680
+ if (last) {
6681
+ const s = engine._load(last.id);
6682
+ if (s && s.bgTitle === 'Beast MCP') {
6683
+ engine.cache.set(s.id, s);
6684
+ mcpChatSid = s.id;
6685
+ return s;
6686
+ }
6687
+ }
6688
+ } catch {}
6689
+ }
6690
+ return mcpChatCreate();
6691
+ }
6692
+
6693
+ /* geçmiş yükü: yalnız metin mesajları (tool çağrıları panele basılmaz) */
6694
+ function mcpChatMessages(s) {
6695
+ const msgs = [];
6696
+ for (const m of s.messages || []) {
6697
+ if (m.tool_calls) continue;
6698
+ const txt = bcMsgText(m);
6699
+ if (!txt) continue;
6700
+ msgs.push({ role: m.role === 'assistant' ? 'assistant' : 'user', text: txt.slice(0, 4000) });
6701
+ }
6702
+ return msgs.slice(-200);
6703
+ }
6704
+
6705
+ /* geçmiş listesi: boş oturumlar gizlenir (aktif olan daima görünür) */
6706
+ function mcpChatHistory() {
6707
+ let items = [];
6708
+ try { items = engine.listMcpSessions(100); } catch {}
6709
+ return items.filter((it) => it.id === mcpChatSid || it.count > 0);
6710
+ }
6711
+
6712
+ function mcpChatQueuePush(text, attachments) {
6713
+ let q = mcpChatQueue.get('mcp');
6714
+ if (!q) {
6715
+ q = { timer: null, msgs: [] };
6716
+ mcpChatQueue.set('mcp', q);
6717
+ }
6718
+ q.msgs.push({
6719
+ text,
6720
+ attachments: Array.isArray(attachments) && attachments.length ? attachments : undefined,
6721
+ });
6722
+ return q;
6723
+ }
6724
+
6725
+ function mcpChatFlush() {
6726
+ const q = mcpChatQueue.get('mcp');
6727
+ if (!q || !q.msgs.length) return;
6728
+ const s = mcpChatSession();
6729
+ if (engine.isBusy(s.id)) return; /* hâlâ çalışıyor — steer yolu mesajı zaten aldı */
6730
+ let merged = '';
6731
+ let mergedAtts = null;
6732
+ for (const m of q.msgs) {
6733
+ if (m.text) merged += (merged ? '\n' : '') + m.text;
6734
+ if (!mergedAtts && Array.isArray(m.attachments) && m.attachments.length) mergedAtts = m.attachments;
6735
+ }
6736
+ mcpChatQueue.delete('mcp');
6737
+ clearTimeout(q.timer);
6738
+ if (!merged.trim() && !mergedAtts) return;
6739
+ try {
6740
+ engine.send(s.id, mergedAtts ? { text: merged, attachments: mergedAtts } : merged, { userAction: true });
6741
+ } catch (e) {
6742
+ /* senkron patlama: panel busy'de kilitlenmesin — hata olayı düş */
6743
+ try {
6744
+ if (win && !win.isDestroyed()) {
6745
+ win.webContents.send('agent:event', { sessionId: s.id, type: 'error', error: String((e && e.message) || e) });
6746
+ }
6747
+ } catch {}
6748
+ }
6749
+ }
6750
+
6751
+ function mcpChatFlushOnDone(ev) {
6752
+ if (!ev || (ev.type !== 'done' && ev.type !== 'error') || !ev.sessionId) return;
6753
+ if (mcpChatQueue.has('mcp') && String(ev.sessionId) === String(mcpChatSid)) {
6754
+ setTimeout(() => { try { mcpChatFlush(); } catch {} }, 150);
6755
+ }
6756
+ }
6757
+
6758
+ ipcMain.handle('mcp:chat:open', async (_e, payload) => {
6759
+ if (!engine) return { ok: false, error: 'ajan hazır değil' };
6760
+ /* bekleyen debounce kuyruğu varsa ÖNCE eski oturuma teslim et (mesaj kaybolmaz) */
6761
+ try {
6762
+ if (mcpChatQueue.has('mcp') && mcpChatSid) mcpChatFlush();
6763
+ } catch {}
6764
+ const q = mcpChatQueue.get('mcp');
6765
+ if (q) {
6766
+ clearTimeout(q.timer);
6767
+ mcpChatQueue.delete('mcp');
6768
+ }
6769
+ const id = String((payload && payload.id) || '');
6770
+ let s;
6771
+ if (id) {
6772
+ try { s = engine._load(id); } catch { return { ok: false, error: 'oturum açılamadı' }; }
6773
+ if (s.bgTitle !== 'Beast MCP') return { ok: false, error: 'bu oturum MCP sohbeti değil' };
6774
+ mcpChatSid = id;
6775
+ mcpChatFresh = false;
6776
+ } else {
6777
+ s = mcpChatSession();
6778
+ }
6779
+ engine.cache.set(s.id, s);
6780
+ return {
6781
+ ok: true,
6782
+ sessionId: s.id,
6783
+ busy: !!engine.isBusy(s.id),
6784
+ messages: mcpChatMessages(s),
6785
+ items: mcpChatHistory(),
6786
+ };
6787
+ });
6788
+
6789
+ ipcMain.handle('mcp:chat:send', async (_e, payload) => {
6790
+ const text = String((payload && payload.msg) || '').trim();
6791
+ const attachments = Array.isArray(payload && payload.attachments) ? payload.attachments : [];
6792
+ if (!text && !attachments.length) return { ok: false, error: 'boş mesaj' };
6793
+ if (!engine) return { ok: false, error: 'ajan hazır değil' };
6794
+ if (!engine.publicState().hasModel) return { ok: false, error: 'model yok — Ayarlar → Provider sekmesinden ekle' };
6795
+ const s = mcpChatSession();
6796
+ if (engine.isBusy(s.id)) {
6797
+ /* MAIN CHAT STEER: koşan tur varken mesaj ANINDA konuşmaya eklenir */
6798
+ engine.send(s.id, attachments.length ? { text, attachments } : text, { userAction: true });
6799
+ return { ok: true, sessionId: s.id, steered: true };
6800
+ }
6801
+ const q = mcpChatQueuePush(text, attachments);
6802
+ clearTimeout(q.timer);
6803
+ q.timer = setTimeout(() => { try { mcpChatFlush(); } catch {} }, MCP_CHAT_DEBOUNCE_MS);
6804
+ return { ok: true, sessionId: s.id, pending: true };
6805
+ });
6806
+
6807
+ ipcMain.handle('mcp:chat:stop', async () => {
6808
+ const q = mcpChatQueue.get('mcp');
6809
+ if (q) {
6810
+ clearTimeout(q.timer);
6811
+ mcpChatQueue.delete('mcp');
6812
+ }
6813
+ const sid = mcpChatSid;
6814
+ const wasBusy = sid ? engine.isBusy(sid) : false;
6815
+ let r = false;
6816
+ if (wasBusy) {
6817
+ try { r = engine.interrupt(sid, 'kullanıcı MCP sohbet panelinden ■ ile durdurdu'); } catch {}
6818
+ }
6819
+ return { ok: true, wasBusy, interrupted: r };
6820
+ });
6821
+
6822
+ ipcMain.handle('mcp:chat:new', async () => {
6823
+ if (!engine) return { ok: false, error: 'ajan hazır değil' };
6824
+ const sid = mcpChatSid;
6825
+ if (sid && engine.isBusy(sid)) return { ok: false, error: 'mesaj sürüyor — önce ■ ile durdur' };
6826
+ const q = mcpChatQueue.get('mcp');
6827
+ if (q) {
6828
+ clearTimeout(q.timer);
6829
+ mcpChatQueue.delete('mcp');
6830
+ }
6831
+ mcpChatSid = '';
6832
+ mcpChatFresh = true; /* sonraki oturum TAZE açılır; eski sohbet geçmişte kalır */
6833
+ return { ok: true, sessionId: '', messages: [], items: mcpChatHistory() };
6834
+ });
6835
+
6836
+ ipcMain.handle('mcp:chat:history', async () => {
6837
+ try {
6838
+ return { ok: true, items: mcpChatHistory() };
6839
+ } catch (e) {
6840
+ return { ok: false, error: String((e && e.message) || e), items: [] };
6841
+ }
6842
+ });
6843
+
6638
6844
  /* ---------------- FALLOUT IPC ---------------- */
6639
6845
 
6640
6846
  function defaultFallout() {
package/src/preload.js CHANGED
@@ -55,6 +55,11 @@ contextBridge.exposeInMainWorld('beast', {
55
55
  mcpConfigGet: () => ipcRenderer.invoke('mcp:config:get'),
56
56
  mcpConfigSet: (raw) => ipcRenderer.invoke('mcp:config:set', raw),
57
57
  mcpRefresh: (name) => ipcRenderer.invoke('mcp:refresh', name),
58
+ mcpChatOpen: (payload) => ipcRenderer.invoke('mcp:chat:open', payload),
59
+ mcpChatSend: (payload) => ipcRenderer.invoke('mcp:chat:send', payload),
60
+ mcpChatStop: () => ipcRenderer.invoke('mcp:chat:stop'),
61
+ mcpChatNew: () => ipcRenderer.invoke('mcp:chat:new'),
62
+ mcpChatHistory: () => ipcRenderer.invoke('mcp:chat:history'),
58
63
  getLimits: () => ipcRenderer.invoke('limits:get'),
59
64
  setLimits: (cfg) => ipcRenderer.invoke('limits:set', cfg),
60
65
  secGet: () => ipcRenderer.invoke('sec:get'),
@@ -483,6 +483,24 @@
483
483
  <textarea id="mcpJson" spellcheck="false"></textarea>
484
484
  </div>
485
485
  </section>
486
+
487
+ <div id="mcpSplit" title="Sürükleyerek boyutlandır"></div>
488
+
489
+ <section id="mcpChat">
490
+ <div id="mcpChatHead">
491
+ <span id="mcpChatTitle">MCP SOHBET</span>
492
+ <select id="mcpChatHist" title="MCP sohbet geçmişi — ana sohbetten ayrıdır"></select>
493
+ <span class="file-spacer"></span>
494
+ <button id="mcpChatNew" title="Yeni sohbet (mevcut sohbet geçmişte kalır)">*</button>
495
+ </div>
496
+ <div id="mcpChatOut"></div>
497
+ <div id="mcpChatStatus" hidden></div>
498
+ <div id="mcpChatInputRow">
499
+ <textarea id="mcpChatInput" rows="1" spellcheck="false" autocomplete="off" placeholder="MCP sohbeti — sunucu araçları ajanın emrinde…"></textarea>
500
+ <button id="mcpChatSend" title="Gönder (Enter)"><svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg></button>
501
+ <button id="mcpChatStop" title="Çalışan mesajı durdur" hidden data-i18n-title="bc_stop"><svg viewBox="0 0 24 24" width="13" height="13" fill="currentColor" stroke="none"><rect x="6" y="6" width="12" height="12" rx="2"/></svg></button>
502
+ </div>
503
+ </section>
486
504
  </div>
487
505
  </main>
488
506
 
@@ -162,6 +162,15 @@ const els = {
162
162
  mcpJsonPath: $('#mcpJsonPath'),
163
163
  mcpSave: $('#mcpSave'),
164
164
  mcpMsg: $('#mcpMsg'),
165
+ mcpSplit: $('#mcpSplit'),
166
+ mcpChat: $('#mcpChat'),
167
+ mcpChatOut: $('#mcpChatOut'),
168
+ mcpChatStatus: $('#mcpChatStatus'),
169
+ mcpChatHist: $('#mcpChatHist'),
170
+ mcpChatNew: $('#mcpChatNew'),
171
+ mcpChatInput: $('#mcpChatInput'),
172
+ mcpChatSend: $('#mcpChatSend'),
173
+ mcpChatStop: $('#mcpChatStop'),
165
174
  stPanel: $('#stPanel'),
166
175
  stTitle: $('#stTitle'),
167
176
  stCwd: $('#stCwd'),
@@ -5951,6 +5960,12 @@ function onEvent(ev) {
5951
5960
  bcLine('t-dim', '⏳ onay bekleniyor: ' + (ev.tool || '?') + ' — IDE modundan çıkıp sohbetteki kartı onayla');
5952
5961
  return;
5953
5962
  }
5963
+ /* Beast MCP sohbet oturumunun onayı: panelde ipucu + kart sohbete düşer */
5964
+ if (mcpChatSid && ev.sessionId === mcpChatSid) {
5965
+ showApprovalCard(ev);
5966
+ mcpLine('t-dim', '⏳ onay bekleniyor: ' + (ev.tool || '?') + ' — ana sohbetteki kartı onayla');
5967
+ return;
5968
+ }
5954
5969
  if (ev.sessionId && ev.sessionId !== activeId) return;
5955
5970
  showApprovalCard(ev);
5956
5971
  return;
@@ -5961,6 +5976,12 @@ function onEvent(ev) {
5961
5976
  if (ev.type === 'permission.asked') {
5962
5977
  const req = ev.request || {};
5963
5978
  const isBc = bcSessionId && ev.sessionId === bcSessionId;
5979
+ /* Beast MCP sohbet oturumunun izni: kart sohbete düşer, panelde ipucu */
5980
+ if (mcpChatSid && ev.sessionId === mcpChatSid) {
5981
+ showPermissionCard(req, false);
5982
+ mcpLine('t-dim', '⏳ izin bekleniyor: ' + (req.permission || '?') + ' — ana sohbetteki kartı yanıtla');
5983
+ return;
5984
+ }
5964
5985
  if (!isBc && ev.sessionId && ev.sessionId !== activeId) return;
5965
5986
  showPermissionCard(req, isBc);
5966
5987
  return;
@@ -6064,6 +6085,12 @@ function onEvent(ev) {
6064
6085
  sbRunIngest(ev);
6065
6086
  return;
6066
6087
  }
6088
+ /* Beast MCP sohbeti: olaylar SADECE MCP modundaki sağ sohbete akar — ana
6089
+ sohbeti ve Code/Studio/Sandbox panellerini kirletmez (dünyalar ayrı) */
6090
+ if (mcpChatSid && ev.sessionId === mcpChatSid) {
6091
+ mcpIngest(ev);
6092
+ return;
6093
+ }
6067
6094
  /* Beast Sandbox oturumu: olaylar SADECE Sandbox paneline akar — ana sohbeti
6068
6095
  ve Code/Studio panellerini kirletmez (dünyalar ayrı) */
6069
6096
  if (sbSessionId && ev.sessionId === sbSessionId) {
@@ -12514,6 +12541,8 @@ async function setMcpMode(on) {
12514
12541
  if (brandSub) brandSub.textContent = on ? 'MCP' : 'Agent';
12515
12542
  if (on) {
12516
12543
  await renderMcpPanel();
12544
+ mcpSplitRestore(); /* sohbet/detay oranı */
12545
+ mcpChatEnsure(); /* ayrı MCP sohbet oturumu (yoksa aç/geçmişten sürdür) */
12517
12546
  if (!mcpTimer) {
12518
12547
  mcpTimer = setInterval(() => { if (mcpModeOn()) renderMcpPanel(); }, 5000);
12519
12548
  }
@@ -12645,6 +12674,284 @@ if (els.mcpAddBtn) els.mcpAddBtn.addEventListener('click', () => {
12645
12674
  } catch {}
12646
12675
  });
12647
12676
 
12677
+ /* ---------- MCP SOHBETİ (sağ panel): ana sohbetten AYRI oturum ----------
12678
+ Oturum main'de bgTitle 'Beast MCP' ile yaşar; olaylar sessionId eşleşmesiyle
12679
+ yalnız bu panele akar (onEvent yönlendirmesi). Geçmiş seçici eski MCP
12680
+ sohbetlerini açar; '*' taze sohbet başlatır (eskisi geçmişte kalır). */
12681
+ let mcpChatSid = '';
12682
+ let mcpChatBusy = false;
12683
+ let mcpChatOpening = false;
12684
+ let mcpChatItems = [];
12685
+ let mcpStreamEl = null;
12686
+ let mcpStreamRaw = '';
12687
+
12688
+ function mcpLine(cls, text) {
12689
+ if (!els.mcpChatOut) return null;
12690
+ const div = document.createElement('div');
12691
+ div.className = cls || 't-out';
12692
+ div.textContent = String(text || '');
12693
+ els.mcpChatOut.appendChild(div);
12694
+ els.mcpChatOut.scrollTop = els.mcpChatOut.scrollHeight;
12695
+ return div;
12696
+ }
12697
+
12698
+ function mcpStreamDelta(delta) {
12699
+ if (!els.mcpChatOut) return;
12700
+ if (!mcpStreamEl) mcpStreamEl = mcpLine('t-out', '');
12701
+ mcpStreamRaw += String(delta || '');
12702
+ mcpStreamEl.textContent = mcpStreamRaw;
12703
+ els.mcpChatOut.scrollTop = els.mcpChatOut.scrollHeight;
12704
+ }
12705
+
12706
+ function mcpFlushStream() {
12707
+ mcpStreamEl = null;
12708
+ mcpStreamRaw = '';
12709
+ }
12710
+
12711
+ function mcpSetBusy(b) {
12712
+ mcpChatBusy = !!b;
12713
+ if (els.mcpChatStop) els.mcpChatStop.hidden = !mcpChatBusy;
12714
+ }
12715
+
12716
+ function mcpStatusShow(text) {
12717
+ if (els.mcpChatStatus) {
12718
+ els.mcpChatStatus.hidden = false;
12719
+ els.mcpChatStatus.textContent = text;
12720
+ }
12721
+ }
12722
+
12723
+ function mcpStatusHide() {
12724
+ if (els.mcpChatStatus) els.mcpChatStatus.hidden = true;
12725
+ }
12726
+
12727
+ /* MCP oturumunun engine olayları → panel (sbIngest'in MCP karşılığı) */
12728
+ function mcpIngest(ev) {
12729
+ if (!mcpChatSid || ev.sessionId !== mcpChatSid) return;
12730
+ switch (ev.type) {
12731
+ case 'token':
12732
+ mcpStreamDelta(ev.delta);
12733
+ break;
12734
+ case 'message': {
12735
+ const m = ev.message;
12736
+ if (!m) break;
12737
+ /* kullanıcı echo basılmaz — gönderirken 'sen>' satırı düştü */
12738
+ if (m.role === 'assistant') {
12739
+ mcpFlushStream();
12740
+ const hasTools = Array.isArray(m.tool_calls) && m.tool_calls.length > 0;
12741
+ const txt = typeof m.content === 'string' ? m.content.trim() : '';
12742
+ if (txt && !hasTools) mcpLine('t-out', txt);
12743
+ }
12744
+ break;
12745
+ }
12746
+ case 'tool-start':
12747
+ mcpSetBusy(true);
12748
+ mcpLine('t-dim', '\u2692 ' + (ev.name || '?') + (sbArgsSummary(ev.args) ? ' \u00B7 ' + sbArgsSummary(ev.args) : ''));
12749
+ break;
12750
+ case 'tool-end': {
12751
+ const ok = ev.ok !== false;
12752
+ mcpLine(ok ? 't-dim' : 't-err', (ok ? ' \u2713 ' : ' \u2717 ') + String(ev.result || '').replace(/\s+/g, ' ').slice(0, 160));
12753
+ break;
12754
+ }
12755
+ case 'status':
12756
+ if (ev.status === 'idle') {
12757
+ mcpSetBusy(false);
12758
+ mcpStatusHide();
12759
+ } else {
12760
+ mcpSetBusy(true);
12761
+ mcpStatusShow(ev.status === 'thinking' ? 'düşünüyor…' : String(ev.status || ''));
12762
+ }
12763
+ break;
12764
+ case 'done':
12765
+ mcpFlushStream();
12766
+ mcpSetBusy(false);
12767
+ mcpStatusHide();
12768
+ mcpLine('t-dim', ev.aborted ? '[durduruldu' + (ev.reason ? ' — ' + ev.reason : '') + ']' : '(tamamlandı)');
12769
+ mcpHistRefresh();
12770
+ break;
12771
+ case 'error':
12772
+ mcpFlushStream();
12773
+ mcpSetBusy(false);
12774
+ mcpStatusHide();
12775
+ mcpLine('t-err', '[hata] ' + (ev.error || ''));
12776
+ break;
12777
+ }
12778
+ }
12779
+
12780
+ function mcpHistFill(items) {
12781
+ if (!els.mcpChatHist) return;
12782
+ mcpChatItems = Array.isArray(items) ? items : [];
12783
+ const sel = mcpChatSid || '';
12784
+ els.mcpChatHist.innerHTML = '';
12785
+ if (!mcpChatItems.length) {
12786
+ const o = document.createElement('option');
12787
+ o.value = '';
12788
+ o.textContent = 'Geçmiş yok';
12789
+ els.mcpChatHist.appendChild(o);
12790
+ els.mcpChatHist.value = '';
12791
+ return;
12792
+ }
12793
+ for (const it of mcpChatItems) {
12794
+ const o = document.createElement('option');
12795
+ o.value = it.id;
12796
+ o.textContent = (it.title || 'MCP sohbeti').slice(0, 64) + (it.count ? ' · ' + it.count : '');
12797
+ els.mcpChatHist.appendChild(o);
12798
+ }
12799
+ if (sel && mcpChatItems.some((x) => x.id === sel)) els.mcpChatHist.value = sel;
12800
+ }
12801
+
12802
+ async function mcpHistRefresh() {
12803
+ const r = await beast.mcpChatHistory().catch(() => null);
12804
+ if (r && r.ok) mcpHistFill(r.items || []);
12805
+ }
12806
+
12807
+ async function mcpChatOpen(id) {
12808
+ if (mcpChatBusy || mcpChatOpening) return;
12809
+ mcpChatOpening = true;
12810
+ const r = await beast.mcpChatOpen(id ? { id } : undefined).catch(() => null);
12811
+ mcpChatOpening = false;
12812
+ if (!r || !r.ok) {
12813
+ mcpLine('t-err', (r && r.error) || 'MCP sohbet oturumu açılamadı');
12814
+ return;
12815
+ }
12816
+ mcpChatSid = r.sessionId || '';
12817
+ if (els.mcpChatOut) els.mcpChatOut.innerHTML = '';
12818
+ mcpFlushStream();
12819
+ for (const m of r.messages || []) {
12820
+ mcpLine(m.role === 'assistant' ? 't-out' : 't-cmd', (m.role === 'assistant' ? '' : 'sen> ') + m.text);
12821
+ }
12822
+ if (!(r.messages || []).length) {
12823
+ mcpLine('t-dim', 'MCP sohbeti hazır — MCP sunucu araçlarını kullanabilen ajanla konuş.');
12824
+ }
12825
+ mcpHistFill(r.items || []);
12826
+ mcpSetBusy(!!r.busy);
12827
+ mcpStatusHide();
12828
+ }
12829
+
12830
+ /* moda girişte: mevcut oturum yoksa aç (çalışan sohbeti ASLA yeniden yükleme) */
12831
+ async function mcpChatEnsure() {
12832
+ if (!mcpChatSid && !mcpChatBusy) await mcpChatOpen();
12833
+ }
12834
+
12835
+ function mcpInputResize() {
12836
+ const ta = els.mcpChatInput;
12837
+ if (!ta) return;
12838
+ ta.style.height = 'auto';
12839
+ ta.style.height = Math.min(ta.scrollHeight, 140) + 'px';
12840
+ ta.classList.toggle('expand', ta.scrollHeight > 140);
12841
+ }
12842
+
12843
+ function mcpRunCurrent() {
12844
+ const msg = els.mcpChatInput.value.trim();
12845
+ if (!msg || mcpChatOpening) return;
12846
+ els.mcpChatInput.value = '';
12847
+ mcpInputResize();
12848
+ mcpLine('t-cmd', 'sen> ' + msg);
12849
+ mcpSetBusy(true);
12850
+ beast.mcpChatSend({ msg }).then((r) => {
12851
+ if (r && r.ok) {
12852
+ if (r.sessionId) mcpChatSid = r.sessionId;
12853
+ if (r.steered) mcpLine('t-dim', '\u23E9 koşan tura eklendi');
12854
+ if (r.queued) mcpLine('t-dim', '\u23F3 kuyrukta (' + r.count + ') — iş bitince gönderilir');
12855
+ } else {
12856
+ mcpSetBusy(false);
12857
+ mcpLine('t-err', (r && r.error) || 'gönderilemedi');
12858
+ }
12859
+ }).catch((e) => {
12860
+ mcpSetBusy(false);
12861
+ mcpLine('t-err', String((e && e.message) || e));
12862
+ });
12863
+ }
12864
+
12865
+ if (els.mcpChatSend) els.mcpChatSend.addEventListener('click', () => {
12866
+ mcpRunCurrent();
12867
+ if (els.mcpChatInput) els.mcpChatInput.focus();
12868
+ });
12869
+ if (els.mcpChatStop) els.mcpChatStop.addEventListener('click', () => {
12870
+ beast.mcpChatStop().then((r) => {
12871
+ if (r && r.ok && r.wasBusy === false) {
12872
+ mcpSetBusy(false);
12873
+ mcpStatusHide();
12874
+ }
12875
+ }).catch(() => {});
12876
+ });
12877
+ if (els.mcpChatNew) els.mcpChatNew.addEventListener('click', async () => {
12878
+ const r = await beast.mcpChatNew().catch(() => null);
12879
+ if (!r || !r.ok) { toast((r && r.error) || 'yeni sohbet açılamadı'); return; }
12880
+ mcpChatSid = '';
12881
+ if (els.mcpChatOut) els.mcpChatOut.innerHTML = '';
12882
+ mcpFlushStream();
12883
+ mcpSetBusy(false);
12884
+ mcpStatusHide();
12885
+ mcpLine('t-dim', 'yeni MCP sohbeti — ilk mesajla başlar');
12886
+ mcpHistFill(r.items || []);
12887
+ if (els.mcpChatInput) els.mcpChatInput.focus();
12888
+ });
12889
+ if (els.mcpChatHist) els.mcpChatHist.addEventListener('change', () => {
12890
+ const id = String(els.mcpChatHist.value || '');
12891
+ if (!id || id === mcpChatSid) return;
12892
+ if (mcpChatBusy) {
12893
+ mcpHistFill(mcpChatItems);
12894
+ toast('mesaj sürüyor — önce ■ ile durdur');
12895
+ return;
12896
+ }
12897
+ mcpChatOpen(id);
12898
+ });
12899
+ if (els.mcpChatInput) {
12900
+ els.mcpChatInput.addEventListener('input', mcpInputResize);
12901
+ els.mcpChatInput.addEventListener('keydown', (e) => {
12902
+ if (e.key === 'Enter' && !e.shiftKey) {
12903
+ e.preventDefault();
12904
+ mcpRunCurrent();
12905
+ }
12906
+ });
12907
+ }
12908
+
12909
+ /* ---------- MCP sohbet/detay ayırıcı (sandbox deseni, sohbet SAĞDA) ----------
12910
+ Kaydedilen değer sohbet panelinin mcpRow içindeki payı (0-1). */
12911
+ let mcpSplitFrac = 0;
12912
+
12913
+ function mcpSplitApplyFrac() {
12914
+ const rowW = els.mcpRow && els.mcpRow.clientWidth ? els.mcpRow.clientWidth : window.innerWidth - 250;
12915
+ if (!mcpSplitFrac) {
12916
+ const w = Math.max(320, Math.min(520, Math.round(rowW * 0.34)));
12917
+ document.body.style.setProperty('--mcpSplit', w + 'px');
12918
+ return;
12919
+ }
12920
+ mcpSplitFrac = Math.max(0.2, Math.min(mcpSplitFrac, 0.85));
12921
+ const w = Math.max(300, Math.min(Math.round(rowW * mcpSplitFrac), Math.max(320, rowW - 240)));
12922
+ document.body.style.setProperty('--mcpSplit', w + 'px');
12923
+ }
12924
+
12925
+ function mcpSplitRestore() {
12926
+ let saved = 0;
12927
+ try { saved = parseFloat(localStorage.getItem('beast.mcpSplit')) || 0; } catch {}
12928
+ mcpSplitFrac = saved > 0.05 && saved < 0.95 ? saved : 0;
12929
+ mcpSplitApplyFrac();
12930
+ }
12931
+
12932
+ if (els.mcpSplit) {
12933
+ els.mcpSplit.addEventListener('mousedown', (e) => {
12934
+ e.preventDefault();
12935
+ els.mcpSplit.classList.add('dragging');
12936
+ const rightEdge = els.mcpChat ? els.mcpChat.getBoundingClientRect().right : window.innerWidth;
12937
+ const rowW = Math.max(600, els.mcpRow && els.mcpRow.clientWidth ? els.mcpRow.clientWidth : window.innerWidth - 250);
12938
+ const move = (ev) => {
12939
+ const w = Math.max(300, Math.min(rightEdge - ev.clientX, rowW - 240));
12940
+ mcpSplitFrac = Math.max(0.2, Math.min(w / rowW, 0.85));
12941
+ document.body.style.setProperty('--mcpSplit', Math.round(w) + 'px');
12942
+ };
12943
+ const up = () => {
12944
+ document.removeEventListener('mousemove', move);
12945
+ document.removeEventListener('mouseup', up);
12946
+ els.mcpSplit.classList.remove('dragging');
12947
+ try { localStorage.setItem('beast.mcpSplit', String(mcpSplitFrac)); } catch {}
12948
+ };
12949
+ document.addEventListener('mousemove', move);
12950
+ document.addEventListener('mouseup', up);
12951
+ });
12952
+ window.addEventListener('resize', () => { if (mcpModeOn()) mcpSplitApplyFrac(); });
12953
+ }
12954
+
12648
12955
  /* ---------- ÇALIŞTIRICI v2: butonlar DETERMİNİSTİK ----------
12649
12956
  Proje tipi algılanır (package.json/requirements.txt/pyproject/Cargo.toml/
12650
12957
  go.mod/hugo/jekyll/mkdocs/index.html); Başlat/Dev gerekirse bağımlılıkları
@@ -4588,7 +4588,7 @@ body.mcp-mode #mcpRow { display: flex; flex-direction: row; }
4588
4588
  display: flex;
4589
4589
  flex-direction: column;
4590
4590
  flex: 1;
4591
- min-width: 0;
4591
+ min-width: 220px;
4592
4592
  min-height: 0;
4593
4593
  background: var(--bg);
4594
4594
  border-left: 1px solid var(--border);
@@ -4764,6 +4764,130 @@ body.mcp-mode #mcpRow { display: flex; flex-direction: row; }
4764
4764
  outline: none;
4765
4765
  white-space: pre;
4766
4766
  }
4767
+
4768
+ /* MCP sağ sohbet paneli: ana sohbetten TAMAMEN AYRI oturum (bgTitle 'Beast MCP') */
4769
+ #mcpChat {
4770
+ display: flex;
4771
+ flex-direction: column;
4772
+ min-height: 0;
4773
+ min-width: 280px;
4774
+ flex: 0 0 var(--mcpSplit, 440px);
4775
+ background: var(--bg);
4776
+ overflow: hidden;
4777
+ }
4778
+ #mcpSplit {
4779
+ flex: none;
4780
+ width: 6px;
4781
+ cursor: col-resize;
4782
+ background: var(--border);
4783
+ opacity: 0.55;
4784
+ transition: opacity 0.15s;
4785
+ }
4786
+ #mcpSplit:hover { opacity: 1; }
4787
+ #mcpChatHead {
4788
+ flex: none;
4789
+ display: flex;
4790
+ align-items: center;
4791
+ gap: 8px;
4792
+ padding: 9px 12px;
4793
+ border-bottom: 1px solid var(--border);
4794
+ background: var(--panel);
4795
+ }
4796
+ #mcpChatTitle { font-size: 11px; font-weight: 800; letter-spacing: 1px; color: var(--accent); flex: none; }
4797
+ #mcpChatHist {
4798
+ flex: 1;
4799
+ min-width: 0;
4800
+ background: var(--panel2);
4801
+ border: 1px solid var(--border);
4802
+ color: var(--text);
4803
+ font-size: 11px;
4804
+ padding: 3px 6px;
4805
+ border-radius: 6px;
4806
+ cursor: pointer;
4807
+ }
4808
+ #mcpChatHist:hover { border-color: var(--accent); }
4809
+ #mcpChatHead button {
4810
+ background: none;
4811
+ border: none;
4812
+ color: var(--muted);
4813
+ font-size: 14px;
4814
+ cursor: pointer;
4815
+ padding: 2px 7px;
4816
+ border-radius: 6px;
4817
+ }
4818
+ #mcpChatHead button:hover { background: var(--panel2); color: var(--text); }
4819
+ #mcpChatOut {
4820
+ flex: 1;
4821
+ min-height: 0;
4822
+ overflow-y: auto;
4823
+ padding: 10px 12px;
4824
+ font-family: Consolas, monospace;
4825
+ font-size: 12px;
4826
+ line-height: 1.5;
4827
+ white-space: pre-wrap;
4828
+ word-break: break-word;
4829
+ }
4830
+ #mcpChatStatus {
4831
+ flex: none;
4832
+ align-self: center;
4833
+ margin: 0 12px 6px;
4834
+ padding: 3px 10px;
4835
+ font-size: 11px;
4836
+ color: var(--accent);
4837
+ background: var(--accent-dim);
4838
+ border: 1px solid var(--accent);
4839
+ border-radius: 999px;
4840
+ max-width: calc(100% - 24px);
4841
+ overflow: hidden;
4842
+ text-overflow: ellipsis;
4843
+ white-space: nowrap;
4844
+ }
4845
+ #mcpChatStatus[hidden] { display: none; }
4846
+ #mcpChatInputRow {
4847
+ flex: none;
4848
+ display: flex;
4849
+ gap: 6px;
4850
+ align-items: flex-end;
4851
+ padding: 8px 12px;
4852
+ border-top: 1px solid var(--border);
4853
+ background: var(--panel);
4854
+ }
4855
+ #mcpChatInput {
4856
+ flex: 1;
4857
+ min-width: 0;
4858
+ resize: none;
4859
+ background: var(--panel2);
4860
+ border: 1px solid var(--border);
4861
+ color: var(--text);
4862
+ font-size: 12.5px;
4863
+ padding: 7px 9px;
4864
+ border-radius: 8px;
4865
+ font-family: inherit;
4866
+ overflow-y: hidden;
4867
+ max-height: 140px;
4868
+ line-height: 1.45;
4869
+ display: block;
4870
+ }
4871
+ #mcpChatInput:focus { border-color: var(--accent); outline: none; }
4872
+ #mcpChatInput.expand { overflow-y: auto; }
4873
+ #mcpChatInputRow button {
4874
+ flex: none;
4875
+ background: var(--panel2);
4876
+ border: 1px solid var(--border);
4877
+ color: var(--text);
4878
+ border-radius: 7px;
4879
+ cursor: pointer;
4880
+ font-size: 11px;
4881
+ padding: 6px 9px;
4882
+ line-height: 1;
4883
+ margin-bottom: 1px;
4884
+ }
4885
+ #mcpChatInputRow button:hover { border-color: var(--accent); }
4886
+ #mcpChatSend { color: var(--accent); }
4887
+ #mcpChatSend:hover { background: var(--accent-dim); }
4888
+ #mcpChatStop { color: var(--err); }
4889
+ #mcpChatStop[hidden] { display: none; }
4890
+ #mcpChatStop:hover { border-color: var(--err); }
4767
4891
  #sbRunBtns {
4768
4892
  flex: none;
4769
4893
  display: flex;