beast-agent 0.20.0 → 0.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.
@@ -70,6 +70,21 @@ const els = {
70
70
  termClear: $('#termClear'),
71
71
  termClose: $('#termClose'),
72
72
  termResize: $('#termResize'),
73
+ bcOut: $('#bcOut'),
74
+ bcCwd: $('#bcCwd'),
75
+ bcInput: $('#bcInput'),
76
+ bcStop: $('#bcStop'),
77
+ bcClear: $('#bcClear'),
78
+ bcNew: $('#bcNew'),
79
+ bcTodoWrap: $('#bcTodoWrap'),
80
+ codeTabs: $('#codeTabs'),
81
+ codeTa: $('#codeTa'),
82
+ codePath: $('#codePath'),
83
+ codeSave: $('#codeSave'),
84
+ ideRow: $('#ideRow'),
85
+ ideSplit: $('#ideSplit'),
86
+ bcPanel: $('#bcPanel'),
87
+ fileCtxMenu: $('#fileCtxMenu'),
73
88
  cronName: $('#cronName'),
74
89
  cronPreset: $('#cronPreset'),
75
90
  cronSchedule: $('#cronSchedule'),
@@ -298,6 +313,15 @@ function addErrorBubble(text) {
298
313
  }
299
314
 
300
315
  /* ---------------- tool cards ---------------- */
316
+ /* Ard arda gelen araç kartları TEK kutuda toplanır (.tool-box): çalışırken
317
+ lacivert outline döner, gövde max 420px scroll'lu. Grup; tool_calls İÇEREN
318
+ iş turlarında açık kalır, saf metin cevapta (normal mesaj) kapanır. */
319
+ let chatToolGroup = null; /* { box, body } */
320
+
321
+ function closeChatToolGroup() {
322
+ if (chatToolGroup) chatToolGroup.box.classList.remove('running');
323
+ chatToolGroup = null;
324
+ }
301
325
 
302
326
  function argSummary(name, args) {
303
327
  try {
@@ -314,6 +338,18 @@ function argSummary(name, args) {
314
338
  function addToolCard(callId, name, args) {
315
339
  streamEl = null;
316
340
  showEmpty(false);
341
+ /* grup yoksa / DOM temizlenmişse yeni kutu aç */
342
+ if (!chatToolGroup || !els.msgs.contains(chatToolGroup.box)) {
343
+ const box = document.createElement('div');
344
+ box.className = 'tool-box running';
345
+ const body = document.createElement('div');
346
+ body.className = 'tool-boxbody';
347
+ box.appendChild(body);
348
+ els.msgs.appendChild(box);
349
+ chatToolGroup = { box, body };
350
+ } else {
351
+ chatToolGroup.box.classList.add('running');
352
+ }
317
353
  const card = document.createElement('div');
318
354
  card.className = 'tool-card';
319
355
  card.dataset.callId = callId || '';
@@ -327,7 +363,8 @@ function addToolCard(callId, name, args) {
327
363
  card.querySelector('.tool-head').addEventListener('click', () => {
328
364
  card.querySelector('.tool-body').classList.toggle('open');
329
365
  });
330
- els.msgs.appendChild(card);
366
+ chatToolGroup.body.appendChild(card);
367
+ chatToolGroup.body.scrollTop = chatToolGroup.body.scrollHeight;
331
368
  scrollDown(true);
332
369
  return card;
333
370
  }
@@ -345,6 +382,10 @@ function finishToolCard(callId, ok, result) {
345
382
  const body = card.querySelector('.tool-body');
346
383
  body.textContent = String(result || '(çıktı yok)').slice(0, 4000);
347
384
  if (String(result || '').length <= 300) body.classList.add('open');
385
+ /* ring burada söndürülmez — iş bitene kadar (closeChatToolGroup) döner */
386
+ if (chatToolGroup && chatToolGroup.body.contains(card)) {
387
+ chatToolGroup.body.scrollTop = chatToolGroup.body.scrollHeight;
388
+ }
348
389
  }
349
390
 
350
391
  /* ---------------- sessions sidebar ---------------- */
@@ -2784,6 +2825,12 @@ function renderAgentsPane() {
2784
2825
  function onEvent(ev) {
2785
2826
  if (ev.type === 'sessions') { refreshSessions(); return; }
2786
2827
  if (ev.type === 'approval') {
2828
+ /* Beast Code oturumunun onayı: panelde ipucu + kart sohbete düşer */
2829
+ if (bcSessionId && ev.sessionId === bcSessionId) {
2830
+ showApprovalCard(ev);
2831
+ bcLine('t-dim', '⏳ onay bekleniyor: ' + (ev.tool || '?') + ' — IDE modundan çıkıp sohbetteki kartı onayla');
2832
+ return;
2833
+ }
2787
2834
  if (ev.sessionId && ev.sessionId !== activeId) return;
2788
2835
  showApprovalCard(ev);
2789
2836
  return;
@@ -2800,6 +2847,13 @@ function onEvent(ev) {
2800
2847
  scheduleAgentsRender();
2801
2848
  return;
2802
2849
  }
2850
+ /* Beast Code oturumu (IDE modu ortasındaki panel): olayları panele akıt,
2851
+ ana sohbeti kirletme */
2852
+ if (bcSessionId && ev.sessionId === bcSessionId) {
2853
+ bcIngest(ev);
2854
+ return;
2855
+ }
2856
+
2803
2857
  if (ev.sessionId && ev.sessionId !== activeId) {
2804
2858
  /* paralel ajan canlı akışı: sohbeti kirletme, sekmede göster */
2805
2859
  if (agentState.runningIds.has(ev.sessionId)) {
@@ -2816,6 +2870,7 @@ function onEvent(ev) {
2816
2870
  els.msgs.innerHTML = '';
2817
2871
  streamEl = null;
2818
2872
  streamRaw = '';
2873
+ closeChatToolGroup();
2819
2874
  showEmpty(true);
2820
2875
  refreshSessions();
2821
2876
  break;
@@ -2825,7 +2880,12 @@ function onEvent(ev) {
2825
2880
  break;
2826
2881
  case 'message':
2827
2882
  if (ev.message.role === 'user') addUserBubble(ev.message.content);
2828
- else if (ev.message.role === 'assistant') finalizeAssistant(ev.message.content);
2883
+ else if (ev.message.role === 'assistant') {
2884
+ finalizeAssistant(ev.message.content);
2885
+ /* tool_calls'lı iş turu grupları açık tutar; saf metin (normal cevap) kapatır */
2886
+ const hasTools = Array.isArray(ev.message.tool_calls) && ev.message.tool_calls.length > 0;
2887
+ if (!hasTools) closeChatToolGroup();
2888
+ }
2829
2889
  break;
2830
2890
  case 'token':
2831
2891
  ensureStreamBubble();
@@ -2869,6 +2929,9 @@ function onEvent(ev) {
2869
2929
  } else {
2870
2930
  toggleRail(!railPrefBeforeBrowser);
2871
2931
  }
2932
+ /* IDE modunda tarayıcı açılır/kapanır/genişlik değişirse row yeniden bölünür —
2933
+ editör + Beast Code ORTAK kırpılıp ORTAK açılır (kullanıcı payı korunur) */
2934
+ if (ideModeOn()) ideSplitApplyFrac();
2872
2935
  if (shown) {
2873
2936
  els.browserBtn.classList.add('on');
2874
2937
  if (ev.url && document.activeElement !== els.bbUrl) {
@@ -2894,11 +2957,13 @@ function onEvent(ev) {
2894
2957
  applyState();
2895
2958
  break;
2896
2959
  case 'done':
2960
+ closeChatToolGroup();
2897
2961
  setBusy(false);
2898
2962
  refreshSessions();
2899
2963
  els.input.focus();
2900
2964
  break;
2901
2965
  case 'error':
2966
+ closeChatToolGroup();
2902
2967
  setBusy(false);
2903
2968
  addErrorBubble(ev.error);
2904
2969
  refreshSessions();
@@ -4315,3 +4380,732 @@ document.querySelectorAll('.stab').forEach((b) =>
4315
4380
  renderStorePane();
4316
4381
  })
4317
4382
  );
4383
+
4384
+
4385
+ /* ================= IDE MODU =================
4386
+ Tek tuş (#ideBtn): sol → dosya gezgini, orta → chat, sağ → dahili tarayıcı (preview).
4387
+ Preview: workspace kökündeki index.html (yoksa ilk *.html) sağdaki tarayıcıda açılır.
4388
+ Dosyaya tıkla → editör; klasöre tıkla → aç/kapa. */
4389
+
4390
+ const IDE_ICONS = {
4391
+ '.html': '🌐', '.htm': '🌐', '.css': '🎨', '.js': '📜', '.mjs': '📜', '.cjs': '📜',
4392
+ '.ts': '📜', '.jsx': '📜', '.tsx': '📜', '.json': '🧾', '.md': '📝', '.txt': '📄',
4393
+ '.py': '🐍', '.ps1': '⚙️', '.bat': '⚙️', '.cmd': '⚙️', '.sh': '⚙️', '.yaml': '🧾',
4394
+ '.yml': '🧾', '.png': '🖼️', '.jpg': '🖼️', '.jpeg': '🖼️', '.gif': '🖼️', '.svg': '🖼️',
4395
+ '.pdf': '📕',
4396
+ };
4397
+
4398
+ const ideState = {
4399
+ cache: new Map(), // rel -> entries[]
4400
+ open: new Set(['']),
4401
+ path: '',
4402
+ };
4403
+
4404
+ function ideModeOn() {
4405
+ return document.body.classList.contains('ide-mode');
4406
+ }
4407
+
4408
+ async function setIdeMode(on) {
4409
+ document.body.classList.toggle('ide-mode', !!on);
4410
+ /* IDE modu oturumluk: uygulama HER AÇILIŞTA agent (chat) modunda başlar */
4411
+ toggleRail(!!on);
4412
+ /* IDE'den çıkış: açık tarayıcı da kapansın — her şey default haline dönsün */
4413
+ if (!on && document.body.classList.contains('browser-open')) {
4414
+ try { beast.toggleBrowser(); } catch {}
4415
+ }
4416
+ if (on) {
4417
+ ideSplitRestore();
4418
+ await loadIdeTree();
4419
+ bcBanner();
4420
+ }
4421
+ }
4422
+
4423
+ /* ---------- editor/chat ayırıcı (VS Code gibi sürükle-bırak) ----------
4424
+ Orantılı model: kaydedilen, Beast Code'un ideRow içindeki PAYI'dır (0-1).
4425
+ Tarayıcı açılınca/kapanınca/genişlik değişince row yeniden bölünür — editör
4426
+ ve Beast Code ORTAK kırpılıp ORTAK açılır (oranlar korunur).
4427
+ Kayıt yoksa default: tarayıcı açıkken 3 eşit parça, kapalıyken 2 eşit parça. */
4428
+ let ideSplitFrac = 0; /* 0 = kayıt yok → 0.5 default */
4429
+
4430
+ function ideSplitApplyFrac() {
4431
+ if (!ideSplitFrac) ideSplitFrac = 0.5;
4432
+ ideSplitFrac = Math.max(0.2, Math.min(ideSplitFrac, 0.8));
4433
+ const rowW = els.ideRow && els.ideRow.clientWidth ? els.ideRow.clientWidth : window.innerWidth - 250;
4434
+ const w = Math.max(280, Math.min(Math.round(rowW * ideSplitFrac), Math.max(320, rowW - 300)));
4435
+ document.body.style.setProperty('--ideSplit', w + 'px');
4436
+ }
4437
+
4438
+ function ideSplitSave() {
4439
+ try { localStorage.setItem('beast.ideSplit', String(ideSplitFrac)); } catch {}
4440
+ }
4441
+
4442
+ function ideSplitRestore() {
4443
+ let saved = 0;
4444
+ try { saved = parseFloat(localStorage.getItem('beast.ideSplit')) || 0; } catch {}
4445
+ const hasUserSetting = saved > 0.05 && saved < 0.95;
4446
+ ideSplitFrac = hasUserSetting ? saved : 0;
4447
+ ideSplitApplyFrac();
4448
+ /* tarayıcı açıksa ve kullanıcı hiç ayar yapmadıysa üç parçayı eşitle */
4449
+ const browserOpen = document.body.classList.contains('browser-open');
4450
+ if (browserOpen && !hasUserSetting) {
4451
+ beast.browserSetWidth(Math.round((window.innerWidth - 250) / 3)).catch(() => {});
4452
+ }
4453
+ }
4454
+
4455
+ if (els.ideSplit) {
4456
+ els.ideSplit.addEventListener('mousedown', (e) => {
4457
+ e.preventDefault();
4458
+ els.ideSplit.classList.add('dragging');
4459
+ /* tarayıcı dock'u drag sırasında sabit — sağ kenar referansı mousedown anında */
4460
+ const rightEdge = els.bcPanel ? els.bcPanel.getBoundingClientRect().right : window.innerWidth;
4461
+ const rowW = Math.max(600, els.ideRow && els.ideRow.clientWidth ? els.ideRow.clientWidth : window.innerWidth - 250);
4462
+ const move = (ev) => {
4463
+ const w = Math.max(280, Math.min(rightEdge - ev.clientX, rowW - 300));
4464
+ ideSplitFrac = Math.max(0.2, Math.min(w / rowW, 0.8));
4465
+ document.body.style.setProperty('--ideSplit', Math.round(w) + 'px');
4466
+ };
4467
+ const up = () => {
4468
+ document.removeEventListener('mousemove', move);
4469
+ document.removeEventListener('mouseup', up);
4470
+ els.ideSplit.classList.remove('dragging');
4471
+ ideSplitSave();
4472
+ };
4473
+ document.addEventListener('mousemove', move);
4474
+ document.addEventListener('mouseup', up);
4475
+ });
4476
+ window.addEventListener('resize', () => {
4477
+ if (ideModeOn()) ideSplitApplyFrac();
4478
+ });
4479
+ }
4480
+
4481
+ async function loadIdeTree() {
4482
+ ideState.cache.clear();
4483
+ await renderFileTree();
4484
+ }
4485
+
4486
+ function fmtFileSize(n) {
4487
+ n = Number(n) || 0;
4488
+ if (n >= 1048576) return (n / 1048576).toFixed(1) + 'M';
4489
+ if (n >= 1024) return Math.round(n / 1024) + 'K';
4490
+ return n ? n + 'B' : '';
4491
+ }
4492
+
4493
+ async function renderFileTree() {
4494
+ const tree = $('#fileTree');
4495
+ if (!tree) return;
4496
+ tree.innerHTML = '';
4497
+ const wsLabel = $('#filePanelPath');
4498
+ const build = async (rel, depth, container) => {
4499
+ let entries = ideState.cache.get(rel);
4500
+ if (!entries) {
4501
+ const r = await beast.ideTree(rel).catch(() => null);
4502
+ if (!r || !r.ok) {
4503
+ if (!rel) container.innerHTML = `<div class="file-empty">${escapeHtml((r && r.error) || 'okunamadı')}</div>`;
4504
+ return;
4505
+ }
4506
+ entries = r.entries;
4507
+ ideState.cache.set(rel, entries);
4508
+ if (r.workspace) {
4509
+ ideState.path = r.workspace;
4510
+ if (wsLabel) { wsLabel.textContent = r.workspace; wsLabel.title = r.workspace; }
4511
+ if (els.bcCwd) { els.bcCwd.textContent = r.workspace; els.bcCwd.title = r.workspace; }
4512
+ bcOnFolderChanged(r.workspace);
4513
+ }
4514
+ }
4515
+ for (const e of entries) {
4516
+ const child = rel ? rel + '/' + e.name : e.name;
4517
+ const row = document.createElement('div');
4518
+ row.className = 'file-row';
4519
+ row.style.paddingLeft = 6 + depth * 14 + 'px';
4520
+ const ico = e.dir ? (ideState.open.has(child) ? '📂' : '📁') : IDE_ICONS[e.name.slice(e.name.lastIndexOf('.')).toLowerCase()] || '📄';
4521
+ row.innerHTML =
4522
+ `<span class="f-ico">${ico}</span>` +
4523
+ `<span class="f-nm" title="${escapeHtml(e.name)}">${escapeHtml(e.name)}</span>` +
4524
+ (e.dir ? '' : `<span class="f-sz">${fmtFileSize(e.size)}</span>`);
4525
+ row.addEventListener('click', () => {
4526
+ if (e.dir) {
4527
+ if (ideState.open.has(child)) ideState.open.delete(child);
4528
+ else ideState.open.add(child);
4529
+ renderFileTree();
4530
+ } else {
4531
+ codeOpen(child);
4532
+ }
4533
+ });
4534
+ row.dataset.rel = child;
4535
+ row.dataset.dir = e.dir ? '1' : '0';
4536
+ container.appendChild(row);
4537
+ if (e.dir && ideState.open.has(child)) {
4538
+ const box = document.createElement('div');
4539
+ container.appendChild(box);
4540
+ await build(child, depth + 1, box);
4541
+ }
4542
+ }
4543
+ };
4544
+ await build('', 0, tree);
4545
+ }
4546
+
4547
+ /* ---------- sekmeli kod editörü (VS Code düzeni) ----------
4548
+ Dosya tıklaması MODAL değil, codePane içinde SEKME açar.
4549
+ Yanında Beast Code chat'i, onun sağındaki preview tarayıcı durur. */
4550
+
4551
+ const codeTabs = []; /* { rel, content, dirty } */
4552
+ let codeActive = -1;
4553
+
4554
+ function codeRenderTabs() {
4555
+ const bar = els.codeTabs;
4556
+ if (!bar) return;
4557
+ bar.innerHTML = '';
4558
+ codeTabs.forEach((t, i) => {
4559
+ const el = document.createElement('div');
4560
+ el.className = 'code-tab' + (i === codeActive ? ' active' : '') + (t.dirty ? ' dirty' : '');
4561
+ el.title = t.rel;
4562
+ el.innerHTML =
4563
+ '<span class="code-tab-name"></span>' +
4564
+ '<button class="code-tab-x" title="Sekmeyi kapat">\u00D7</button>';
4565
+ el.querySelector('.code-tab-name').textContent = t.rel;
4566
+ el.addEventListener('click', () => codeActivate(i));
4567
+ el.querySelector('.code-tab-x').addEventListener('click', (e) => {
4568
+ e.stopPropagation();
4569
+ codeClose(i);
4570
+ });
4571
+ bar.appendChild(el);
4572
+ });
4573
+ }
4574
+
4575
+ function codeFlushActive() {
4576
+ if (codeActive >= 0 && codeTabs[codeActive]) {
4577
+ codeTabs[codeActive].content = els.codeTa.value;
4578
+ }
4579
+ }
4580
+
4581
+ function codeActivate(i) {
4582
+ codeFlushActive();
4583
+ if (i < 0 || i >= codeTabs.length) {
4584
+ codeActive = -1;
4585
+ els.codeTa.value = '';
4586
+ els.codePath.textContent = '\u2014';
4587
+ codeRenderTabs();
4588
+ return;
4589
+ }
4590
+ codeActive = i;
4591
+ const t = codeTabs[i];
4592
+ els.codeTa.value = t.content;
4593
+ els.codePath.textContent = t.rel;
4594
+ els.codePath.title = (ideState.path || '') + '\\' + t.rel.replace(/\//g, '\\');
4595
+ codeRenderTabs();
4596
+ }
4597
+
4598
+ async function codeOpen(rel) {
4599
+ const idx = codeTabs.findIndex((t) => t.rel === rel);
4600
+ if (idx >= 0) {
4601
+ codeActivate(idx);
4602
+ return;
4603
+ }
4604
+ const r = await beast.ideRead(rel).catch(() => null);
4605
+ if (!r || !r.ok) {
4606
+ toast((r && r.error) || 'okunamad\u0131');
4607
+ return;
4608
+ }
4609
+ codeTabs.push({ rel, content: r.content || '', dirty: false });
4610
+ codeActivate(codeTabs.length - 1);
4611
+ els.codeTa.focus();
4612
+ }
4613
+
4614
+ function codeClose(i) {
4615
+ if (i < 0 || i >= codeTabs.length) return;
4616
+ codeFlushActive();
4617
+ const oldActive = codeActive;
4618
+ const wasActive = i === oldActive;
4619
+ codeTabs.splice(i, 1);
4620
+ if (codeTabs.length === 0) {
4621
+ codeActive = -1;
4622
+ els.codeTa.value = '';
4623
+ els.codePath.textContent = '\u2014';
4624
+ codeRenderTabs();
4625
+ return;
4626
+ }
4627
+ let next = oldActive;
4628
+ if (wasActive) next = Math.min(i, codeTabs.length - 1);
4629
+ else if (i < oldActive) next = oldActive - 1;
4630
+ codeActive = -1; /* activate içindeki flush, eski içerikle yeni sekmeyi ezmesin */
4631
+ codeActivate(next);
4632
+ }
4633
+
4634
+ async function codeSave() {
4635
+ if (codeActive < 0 || !codeTabs[codeActive]) return;
4636
+ codeFlushActive();
4637
+ const t = codeTabs[codeActive];
4638
+ const r = await beast.ideWrite(t.rel, t.content).catch(() => null);
4639
+ if (r && r.ok) {
4640
+ t.dirty = false;
4641
+ codeRenderTabs();
4642
+ toast(_t('ide_saved'));
4643
+ } else {
4644
+ toast((r && r.error) || 'kaydedilemedi');
4645
+ }
4646
+ }
4647
+
4648
+ $('#ideBtn').addEventListener('click', () => setIdeMode(!ideModeOn()));
4649
+ $('#filePick').addEventListener('click', async () => {
4650
+ const r = await beast.ideSetRoot().catch(() => null);
4651
+ if (r && r.ok) {
4652
+ ideState.open = new Set(['']);
4653
+ await loadIdeTree();
4654
+ } else if (r && !r.canceled && r.error) {
4655
+ toast(r.error);
4656
+ }
4657
+ });
4658
+ $('#fileRefresh').addEventListener('click', () => loadIdeTree());
4659
+ $('#filePreview').addEventListener('click', async () => {
4660
+ const r = await beast.idePreview().catch(() => null);
4661
+ if (r && r.ok) {
4662
+ if (ideModeOn() === false) setIdeMode(true);
4663
+ } else {
4664
+ toast((r && r.error) || 'preview açılamadı');
4665
+ }
4666
+ });
4667
+ if (els.codeSave) els.codeSave.addEventListener('click', codeSave);
4668
+ if (els.codeTa) {
4669
+ els.codeTa.addEventListener('input', () => {
4670
+ if (codeActive < 0) return;
4671
+ codeTabs[codeActive].dirty = true;
4672
+ const el = els.codeTabs.children[codeActive];
4673
+ if (el) el.classList.add('dirty');
4674
+ });
4675
+ els.codeTa.addEventListener('keydown', (e) => {
4676
+ if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 's') {
4677
+ e.preventDefault();
4678
+ codeSave();
4679
+ }
4680
+ });
4681
+ }
4682
+
4683
+ /* ---------- dosya ağacı sağ tık menüsü (Sil / Aç / Preview) ---------- */
4684
+
4685
+ function fileCtxHide() {
4686
+ if (els.fileCtxMenu) els.fileCtxMenu.hidden = true;
4687
+ }
4688
+
4689
+ function fileCtxShow(e, rel, isDir) {
4690
+ const menu = els.fileCtxMenu;
4691
+ if (!menu || !rel) return;
4692
+ e.preventDefault();
4693
+ const isHtml = /\.html?$/i.test(rel);
4694
+ const items = [];
4695
+ if (isDir) {
4696
+ items.push({
4697
+ label: ideState.open.has(rel) ? 'Kapat' : 'Aç',
4698
+ fn: () => {
4699
+ if (ideState.open.has(rel)) ideState.open.delete(rel);
4700
+ else ideState.open.add(rel);
4701
+ renderFileTree();
4702
+ },
4703
+ });
4704
+ } else {
4705
+ items.push({ label: 'Aç', fn: () => codeOpen(rel) });
4706
+ if (isHtml) {
4707
+ items.push({
4708
+ label: 'Preview',
4709
+ fn: async () => {
4710
+ const r = await beast.idePreviewFile(rel).catch(() => null);
4711
+ if (!r || !r.ok) toast((r && r.error) || 'preview açılamadı');
4712
+ },
4713
+ });
4714
+ }
4715
+ }
4716
+ items.push({
4717
+ label: 'Sil',
4718
+ danger: true,
4719
+ fn: async () => {
4720
+ const r = await beast.ideDelete(rel).catch(() => null);
4721
+ if (r && r.ok) {
4722
+ /* silinen dosya/klasörün açık sekmelerini kapat */
4723
+ for (let i = codeTabs.length - 1; i >= 0; i--) {
4724
+ if (codeTabs[i].rel === rel || codeTabs[i].rel.startsWith(rel + '/')) codeClose(i);
4725
+ }
4726
+ await loadIdeTree();
4727
+ } else if (r && !r.canceled && r.error) {
4728
+ toast(r.error);
4729
+ }
4730
+ },
4731
+ });
4732
+
4733
+ menu.innerHTML = '';
4734
+ for (const it of items) {
4735
+ const el = document.createElement('div');
4736
+ el.className = 'ctx-item' + (it.danger ? ' danger' : '');
4737
+ el.textContent = it.label;
4738
+ el.addEventListener('click', () => {
4739
+ fileCtxHide();
4740
+ it.fn();
4741
+ });
4742
+ menu.appendChild(el);
4743
+ }
4744
+ menu.hidden = false;
4745
+ const mh = items.length * 33 + 8;
4746
+ menu.style.left = Math.max(4, Math.min(e.clientX, window.innerWidth - 170)) + 'px';
4747
+ menu.style.top = Math.max(4, Math.min(e.clientY, window.innerHeight - mh)) + 'px';
4748
+ }
4749
+
4750
+ if (els.fileCtxMenu) {
4751
+ document.addEventListener('click', fileCtxHide);
4752
+ window.addEventListener('blur', fileCtxHide);
4753
+ window.addEventListener('keydown', (e) => {
4754
+ if (e.key === 'Escape') fileCtxHide();
4755
+ });
4756
+ }
4757
+ $('#fileTree').addEventListener('contextmenu', (e) => {
4758
+ const row = e.target.closest('.file-row');
4759
+ if (!row || !row.dataset.rel) {
4760
+ fileCtxHide();
4761
+ return;
4762
+ }
4763
+ fileCtxShow(e, row.dataset.rel, row.dataset.dir === '1');
4764
+ });
4765
+
4766
+ /* ---------- IDE modu ortası: BEAST CODE paneli ----------
4767
+ Sohbet yerine Beast'in kendi ajanı (varsayılan model) çalışır;
4768
+ soldaki dosya panelindeki klasörü workspace olarak kullanır.
4769
+ Gizli engine oturumu → olaylar onEvent içinde panele yönlendirilir. */
4770
+
4771
+ const BC_MAX_LINES = 1200;
4772
+ let bcSessionId = null;
4773
+ let bcRunning = false;
4774
+ let bcBannerDone = false;
4775
+ let bcStreamEl = null;
4776
+ let bcStreamRaw = '';
4777
+
4778
+ function bcScroll(force) {
4779
+ const b = els.bcOut;
4780
+ if (!b) return;
4781
+ /* her yeni içerikte son mesaja doğru otomatik kaydır */
4782
+ b.scrollTop = b.scrollHeight;
4783
+ }
4784
+
4785
+ /* kutu içi gövdeyi (bc-toolbody) de son bölüme kaydır */
4786
+ function bcBodyScroll(el) {
4787
+ const b = el && el.closest ? el.closest('.bc-toolbody') : null;
4788
+ if (b) b.scrollTop = b.scrollHeight;
4789
+ }
4790
+
4791
+ function bcLine(cls, text, time = false) {
4792
+ if (!els.bcOut) return;
4793
+ const el = document.createElement('div');
4794
+ el.className = 't-line ' + (cls || '');
4795
+ const ts = time ? '<span class="t-time">' + new Date().toTimeString().slice(0, 8) + '</span> ' : '';
4796
+ el.innerHTML = ts + escapeHtml(String(text ?? ''));
4797
+ els.bcOut.appendChild(el);
4798
+ while (els.bcOut.childElementCount > BC_MAX_LINES) els.bcOut.removeChild(els.bcOut.firstChild);
4799
+ bcScroll();
4800
+ }
4801
+
4802
+ function bcBanner() {
4803
+ if (bcBannerDone) return;
4804
+ bcBannerDone = true;
4805
+ const cwd = ideState.path || '';
4806
+ if (els.bcCwd && cwd) { els.bcCwd.textContent = cwd; els.bcCwd.title = cwd; }
4807
+ bcLine('t-sys', 'BEAST CODE — çalışma klasörü: ' + (cwd || '?'));
4808
+ bcLine('t-dim', 'Beast\u2019in varsayılan modeliyle kod yazar; yazışma bu klasöre bağlıdır. + yeni oturum · Temizle çıktıyı siler.', false);
4809
+ }
4810
+
4811
+ /* Klasör değişimi (Klasör seç / kök değişti) → panel taze başlar;
4812
+ oturumlar main tarafında klasör bazlı tutulur (klasöre dönüşte aynı sohbet sürer).
4813
+ bcSessionId kasıtlı sıfırlanmaz: klasör değişse bile çalışan işin done/idle
4814
+ sinyali panele ulaşıp kilidi açar. */
4815
+ let bcWsPath = '';
4816
+
4817
+ function bcOnFolderChanged(p) {
4818
+ if (!p || bcWsPath === p) return;
4819
+ const first = !bcWsPath;
4820
+ bcWsPath = p;
4821
+ if (first) return; /* ilk yükleme — panel zaten boş, banner setIdeMode'da basılır */
4822
+ if (els.bcOut) els.bcOut.innerHTML = '';
4823
+ bcTools.clear();
4824
+ bcCloseToolGroup();
4825
+ bcHideTodos();
4826
+ bcStreamEl = null;
4827
+ bcStreamRaw = '';
4828
+ bcBannerDone = false;
4829
+ bcBanner();
4830
+ }
4831
+
4832
+ function bcSetBusy(v) {
4833
+ bcRunning = !!v;
4834
+ if (els.bcStop) els.bcStop.hidden = !v;
4835
+ }
4836
+
4837
+ function bcFlushStream() {
4838
+ bcStreamEl = null;
4839
+ bcStreamRaw = '';
4840
+ }
4841
+
4842
+ function bcStreamDelta(delta) {
4843
+ if (!bcStreamEl) {
4844
+ bcStreamEl = document.createElement('div');
4845
+ bcStreamEl.className = 't-line t-out';
4846
+ els.bcOut.appendChild(bcStreamEl);
4847
+ }
4848
+ bcStreamRaw += String(delta || '');
4849
+ bcStreamEl.textContent = bcStreamRaw.slice(-4000);
4850
+ bcScroll(true);
4851
+ }
4852
+
4853
+ /* İş bitti → preview açık ve IDE modundaysa workspace sayfasını otomatik yenile */
4854
+ let bcPrevAt = 0;
4855
+
4856
+ function bcRefreshPreview() {
4857
+ const now = Date.now();
4858
+ if (now - bcPrevAt < 1500) return; /* done + idle çift tetiklemesin */
4859
+ bcPrevAt = now;
4860
+ if (!ideModeOn() || !document.body.classList.contains('browser-open')) return;
4861
+ beast.idePreview().catch(() => {});
4862
+ }
4863
+
4864
+ /* ---------- Beast Code araç kutuları ----------
4865
+ Ard arda gelen araç çağrıları TEK kutuda toplanır. Kural: engine mesajı
4866
+ tool_calls İÇERİYORSA (iş turu) grup AÇIK kalır — aradaki ara yazılar kutu
4867
+ içine not olarak akar; tool_calls İÇERMEYEN saf metin (normal mesaj) gelirse
4868
+ grup kapanır, sonraki çağrılar YENİ kutu açar.
4869
+ Kutu çalışırken etrafında fosforlu açık mavi outline döner; her çağrı kutu
4870
+ içinde bir bölüm, çıktı max 180px scroll'lu. */
4871
+ const bcTools = new Map(); /* callId → { box, sec } */
4872
+ let bcCurBox = null; /* açık araç grubu — ard arda çağrılar buraya eklenir */
4873
+ let bcNoteEl = null; /* grup içindeki ara yazı notu */
4874
+ let bcNoteRaw = '';
4875
+
4876
+ function bcNoteDetach() {
4877
+ bcNoteEl = null;
4878
+ bcNoteRaw = '';
4879
+ }
4880
+
4881
+ function bcCloseToolGroup() {
4882
+ /* grup kapanınca (normal mesaj / iş bitti) ring durur */
4883
+ if (bcCurBox) bcCurBox.classList.remove('running');
4884
+ bcCurBox = null;
4885
+ bcNoteDetach();
4886
+ }
4887
+
4888
+ function bcToolGroupNew() {
4889
+ const box = document.createElement('div');
4890
+ box.className = 'bc-toolbox';
4891
+ const body = document.createElement('div');
4892
+ body.className = 'bc-toolbody';
4893
+ box.appendChild(body);
4894
+ els.bcOut.appendChild(box);
4895
+ while (els.bcOut.childElementCount > BC_MAX_LINES) els.bcOut.removeChild(els.bcOut.firstChild);
4896
+ return box;
4897
+ }
4898
+
4899
+ function bcToolBoxStart(callId, name, args) {
4900
+ bcFlushStream();
4901
+ if (!bcCurBox) bcCurBox = bcToolGroupNew();
4902
+ const sec = document.createElement('div');
4903
+ sec.className = 'bc-toolsection';
4904
+ const short = termShortTool(name, args);
4905
+ sec.innerHTML =
4906
+ '<div class="bc-toolhead">' +
4907
+ '<span class="bc-toolspin"></span>' +
4908
+ '<span class="bc-toolname">▸ ' + escapeHtml(String(name || 'araç')) + '</span>' +
4909
+ (short ? '<span class="bc-toolar" title="' + escapeHtml(short) + '">' + escapeHtml(short) + '</span>' : '') +
4910
+ '</div>' +
4911
+ '<div class="bc-toolout" hidden></div>';
4912
+ /* ring: iş bitene / grup kapanana kadar bu kutuda dönmeye devam eder */
4913
+ bcCurBox.classList.add('running');
4914
+ const body = bcCurBox.querySelector('.bc-toolbody') || bcCurBox;
4915
+ body.appendChild(sec);
4916
+ if (callId) bcTools.set(callId, { box: bcCurBox, sec });
4917
+ bcScroll();
4918
+ bcBodyScroll(body);
4919
+ }
4920
+
4921
+ function bcToolBoxEnd(callId, ok, result) {
4922
+ bcFlushStream();
4923
+ const t = (callId && bcTools.get(callId)) || null;
4924
+ if (callId) bcTools.delete(callId);
4925
+ const out = String(result || '').replace(/\s+$/, '');
4926
+ if (t) {
4927
+ const { sec } = t;
4928
+ /* ring burada SÖNDÜRÜLMEZ — iş bitene kadar (bcCloseToolGroup) döner */
4929
+ sec.classList.add('done');
4930
+ if (!ok) sec.classList.add('failed');
4931
+ const pre = sec.querySelector('.bc-toolout');
4932
+ if (out) {
4933
+ pre.hidden = false;
4934
+ pre.textContent = out.length > 4000 ? out.slice(0, 4000) + '\n… (kesildi)' : out;
4935
+ } else {
4936
+ pre.remove();
4937
+ }
4938
+ bcBodyScroll(sec);
4939
+ } else if (out || !ok) {
4940
+ /* başlangıcı kaçmış çağrı — düz satır olarak düş */
4941
+ bcLine(ok ? 't-out' : 't-err', out ? (out.length > 400 ? out.slice(0, 400) + ' …(kesildi)' : out) : '(hata)');
4942
+ }
4943
+ bcScroll();
4944
+ }
4945
+
4946
+ /* ---------- Beast Code todo kartı ----------
4947
+ Inputun (chat) hemen üstünde ortalı kompakt kutu; #bcTodoWrap içinde yaşar,
4948
+ scroll'lu çıktı akışını kirletmez. */
4949
+ function bcRenderTodos(todos) {
4950
+ const list = Array.isArray(todos) ? todos : [];
4951
+ if (!els.bcTodoWrap) return;
4952
+ bcFlushStream();
4953
+ if (!list.length) {
4954
+ bcHideTodos();
4955
+ return;
4956
+ }
4957
+ els.bcTodoWrap.hidden = false;
4958
+ els.bcTodoWrap.innerHTML =
4959
+ '<div class="bc-todobox">' +
4960
+ '<div class="bc-todotitle"><span>GÖREVLER</span><span class="bc-todocount"></span></div>' +
4961
+ '<div class="bc-todoitems"></div>';
4962
+ const done = list.filter((t) => t.status === 'done').length;
4963
+ els.bcTodoWrap.querySelector('.bc-todocount').textContent = done + '/' + list.length;
4964
+ const wrap = els.bcTodoWrap.querySelector('.bc-todoitems');
4965
+ wrap.innerHTML = '';
4966
+ for (const t of list) {
4967
+ const st = TODO_GLYPH[t.status] ? t.status : 'pending';
4968
+ const row = document.createElement('div');
4969
+ row.className = 'bc-todoitem ' + st;
4970
+ row.innerHTML =
4971
+ '<span class="bc-todocheck">' + TODO_GLYPH[st] + '</span>' +
4972
+ '<span class="bc-todotext"></span>';
4973
+ row.querySelector('.bc-todotext').textContent = String(t.title || '');
4974
+ wrap.appendChild(row);
4975
+ }
4976
+ }
4977
+
4978
+ function bcHideTodos() {
4979
+ if (els.bcTodoWrap) {
4980
+ els.bcTodoWrap.hidden = true;
4981
+ els.bcTodoWrap.innerHTML = '';
4982
+ }
4983
+ }
4984
+
4985
+ /* Ara yazı → açık grup varsa kutu İÇİNE not olarak akar (kronoloji korunur) */
4986
+ function bcNoteStream(delta) {
4987
+ if (!bcCurBox) return false;
4988
+ if (!bcNoteEl) {
4989
+ bcNoteEl = document.createElement('div');
4990
+ bcNoteEl.className = 'bc-toolnote';
4991
+ const body = bcCurBox.querySelector('.bc-toolbody') || bcCurBox;
4992
+ body.appendChild(bcNoteEl);
4993
+ }
4994
+ bcNoteRaw += String(delta || '');
4995
+ bcNoteEl.textContent = bcNoteRaw.slice(-2000);
4996
+ bcScroll(true);
4997
+ bcBodyScroll(bcNoteEl);
4998
+ return true;
4999
+ }
5000
+
5001
+ /* Beast Code oturumunun engine olayları → panel çıktısı */
5002
+ function bcIngest(ev) {
5003
+ switch (ev.type) {
5004
+ case 'token':
5005
+ /* grup açıksa ara not (kutu içi); değilse normal akış */
5006
+ if (bcNoteStream(ev.delta)) break;
5007
+ bcStreamDelta(ev.delta);
5008
+ break;
5009
+ case 'message':
5010
+ if (ev.message && ev.message.role === 'assistant') {
5011
+ const content = typeof ev.message.content === 'string' ? ev.message.content : '';
5012
+ const hasTools = Array.isArray(ev.message.tool_calls) && ev.message.tool_calls.length > 0;
5013
+ if (hasTools) {
5014
+ /* iş turu: grup AÇIK kalır — ard arda araçlar aynı kutuda sürer.
5015
+ Kutu içine akmış ara not kalıcı olur, işaretçileri bırak */
5016
+ bcNoteDetach();
5017
+ bcFlushStream();
5018
+ break;
5019
+ }
5020
+ /* NORMAL MESAJ: kutu içine düşen taslak notu çıkar, cevabı düz metin bas */
5021
+ if (bcNoteEl) { bcNoteEl.remove(); bcNoteDetach(); }
5022
+ if (content && !bcStreamRaw.trim()) bcLine('t-out', content);
5023
+ bcCloseToolGroup();
5024
+ bcFlushStream();
5025
+ }
5026
+ break;
5027
+ case 'todos':
5028
+ bcRenderTodos(ev.todos);
5029
+ break;
5030
+ case 'tool-start':
5031
+ bcToolBoxStart(ev.callId, ev.name, ev.args);
5032
+ break;
5033
+ case 'tool-end':
5034
+ bcToolBoxEnd(ev.callId, ev.ok, ev.result);
5035
+ break;
5036
+ case 'done':
5037
+ bcFlushStream();
5038
+ bcCloseToolGroup();
5039
+ bcSetBusy(false);
5040
+ bcLine('t-dim', '(tamamlandı)');
5041
+ bcRefreshPreview();
5042
+ if (ideModeOn() && els.bcInput) els.bcInput.focus();
5043
+ break;
5044
+ case 'error':
5045
+ bcFlushStream();
5046
+ bcCloseToolGroup();
5047
+ bcSetBusy(false);
5048
+ bcLine('t-err', '[hata] ' + (ev.error || ''));
5049
+ break;
5050
+ case 'status':
5051
+ /* 'idle' = engine oturumu GERÇEKTEN bıraktı (finally bloğu, her yolda gelir) —
5052
+ done kaçsa bile panel kilitli kalmaz, ■ otomatik kalkar, ring durur */
5053
+ if (ev.status === 'idle') {
5054
+ bcCloseToolGroup();
5055
+ bcSetBusy(false);
5056
+ bcRefreshPreview();
5057
+ }
5058
+ break;
5059
+ }
5060
+ }
5061
+
5062
+ function bcRunCurrent() {
5063
+ const msg = els.bcInput.value.trim();
5064
+ if (!msg) return;
5065
+ els.bcInput.value = '';
5066
+ bcLine('t-cmd', 'code> ' + msg);
5067
+ bcHideTodos(); /* yeni sorgu = eski todo list temizlenir; agent yeniden basacak */
5068
+ bcSetBusy(true);
5069
+ /* sürüyor/serbest kararını main'deki gerçek engine.isBusy verir —
5070
+ panel bayat kilitli kaldıysa ilk mesajla kendini düzeltir */
5071
+ beast.beastcodeSend(msg).then((r) => {
5072
+ if (r && r.ok) {
5073
+ bcSessionId = r.sessionId;
5074
+ } else if (r && r.busy) {
5075
+ /* iş gerçekten sürüyor — kilit açılmaz, bilgi satırı düşer */
5076
+ bcLine('t-dim', (r && r.error) || 'mesaj sürüyor');
5077
+ } else {
5078
+ bcSetBusy(false);
5079
+ bcLine('t-err', (r && r.error) || 'gönderilemedi');
5080
+ }
5081
+ }).catch((e) => {
5082
+ bcSetBusy(false);
5083
+ bcLine('t-err', String((e && e.message) || e));
5084
+ });
5085
+ }
5086
+
5087
+ if (els.bcInput) els.bcInput.addEventListener('keydown', (e) => {
5088
+ if (e.key === 'Enter') { e.preventDefault(); bcRunCurrent(); }
5089
+ });
5090
+ if (els.bcStop) els.bcStop.addEventListener('click', () => beast.beastcodeStop().catch(() => {}));
5091
+ if (els.bcClear) els.bcClear.addEventListener('click', () => {
5092
+ els.bcOut.innerHTML = '';
5093
+ bcTools.clear();
5094
+ bcCloseToolGroup();
5095
+ bcHideTodos();
5096
+ bcFlushStream();
5097
+ });
5098
+ if (els.bcNew) els.bcNew.addEventListener('click', async () => {
5099
+ if (bcRunning) { toast('Mesaj sürüyor — önce ■ ile durdur'); return; }
5100
+ const r = await beast.beastcodeNew().catch(() => null);
5101
+ if (r && r.ok) {
5102
+ bcSessionId = null;
5103
+ bcTools.clear();
5104
+ bcCloseToolGroup();
5105
+ bcHideTodos();
5106
+ bcFlushStream();
5107
+ bcLine('t-sys', 'yeni oturum — sonraki mesaj taze başlar');
5108
+ }
5109
+ });
5110
+ /* Uygulama her açılışta agent (chat) modunda başlar — IDE modu yalnız
5111
+ kullanıcı butona basınca girilir (oturumluk). */