beast-agent 0.20.0 → 0.22.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 ---------------- */
@@ -1678,7 +1719,8 @@ async function renderUpdatePane(autoCheck) {
1678
1719
  const btnInstall = pane.querySelector('#upInstall');
1679
1720
  if (btnInstall) btnInstall.addEventListener('click', async () => {
1680
1721
  const r = await beast.updateInstall().catch(() => ({ ok: false, error: 'ipc' }));
1681
- if (!r.ok && r.error) toast(r.error);
1722
+ if (r.ok && r.npm) toast(_t('up_npm_started'));
1723
+ else if (!r.ok && r.error) toast(r.error);
1682
1724
  });
1683
1725
 
1684
1726
  /* indirme ilerlemesi için sekme açıkken canlı tazele — YALNIZ durum kutusu */
@@ -2183,9 +2225,44 @@ function renderBotPage() {
2183
2225
  else if (tab === 'log') renderBotLog(pane, b);
2184
2226
  else if (tab === 'watcher') renderBotWatcher(pane, b);
2185
2227
  else if (tab === 'stats') renderBotStats(pane, b);
2228
+ else if (tab === 'notes') renderBotNotes(pane, b);
2186
2229
  renderBotChats(b);
2187
2230
  }
2188
2231
 
2232
+ /* --- Notlar sekmesi: oturum notları — konuşma kodu, başlık, tarih ve özet metni.
2233
+ Admin bot (beast) tüm oturumları görür; müşteri botu yalnız kendi oturumlarını. --- */
2234
+ async function renderBotNotes(pane, b) {
2235
+ pane.innerHTML = `<h2>${_t('notes_h2')}</h2><div class="sub">${_t('notes_sub')}</div>`;
2236
+ let all = [];
2237
+ try { all = await beast.listNotes(); } catch {}
2238
+ const list = (b.admin ? all : all.filter((n) => (n.botId || 'beast') === b.id))
2239
+ .slice()
2240
+ .sort((x, y) => String(y.updatedAt).localeCompare(String(x.updatedAt)));
2241
+ if (!list.length) {
2242
+ pane.insertAdjacentHTML('beforeend', `<div class="mini-empty">${_t('notes_empty')}</div>`);
2243
+ return;
2244
+ }
2245
+ const f = document.createElement('div');
2246
+ f.className = 'bot-form';
2247
+ for (const n of list) {
2248
+ const when = n.updatedAt ? new Date(n.updatedAt).toLocaleString('tr-TR', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' }) : '';
2249
+ const card = document.createElement('div');
2250
+ card.className = 'notes-card';
2251
+ card.innerHTML =
2252
+ `<div class="notes-head"><div class="skill-name">${escapeHtml((b.icon || '') + ' ' + (n.code || '?'))} — ${escapeHtml(n.title || '')}</div>` +
2253
+ `<div class="notes-meta">${escapeHtml(when)} · ${n.count || 0} ${_t('bot_stat_msgs')}</div></div>` +
2254
+ `<div class="notes-body">${escapeHtml(String(n.notes || '').slice(0, 4000))}</div>` +
2255
+ `<div style="margin-top:8px"><button class="btn ghost notes-del">${_t('notes_del')}</button></div>`;
2256
+ card.querySelector('.notes-del').addEventListener('click', async () => {
2257
+ await beast.clearNotes(n.id).catch(() => {});
2258
+ toast(_t('deleted'));
2259
+ renderBotNotes(pane, b);
2260
+ });
2261
+ f.appendChild(card);
2262
+ }
2263
+ pane.appendChild(f);
2264
+ }
2265
+
2189
2266
  function renderBotOverview(pane) {
2190
2267
  pane.innerHTML = `<h2>${_t('bot_overview')}</h2><div class="sub">${_t('bot_overview_sub')}</div>`;
2191
2268
  const totals = { numbers: 0, sessions: 0, msgs: 0 };
@@ -2272,6 +2349,12 @@ function renderBotSettings(pane, b) {
2272
2349
  const skillChecks = BOT_SKILLS
2273
2350
  .map(([k, lbl]) => `<label><input type="checkbox" data-skill="${k}" ${(b.skills || {})[k] ? 'checked' : ''}/> ${lbl}</label>`)
2274
2351
  .join('');
2352
+ /* Yetki: 'all' tek başına tüm araçları verir (özel); web/read/chat çoklu seçilebilir */
2353
+ const PERM_OPTS = [['all', _t('wa_perm_all')], ['web', 'Web'], ['read', _t('wa_perm_read')], ['chat', _t('wa_perm_chat')]];
2354
+ const curPerms = Array.isArray(b.perm) ? b.perm : [b.perm || 'all'];
2355
+ const permChecks = PERM_OPTS
2356
+ .map(([k, lbl]) => `<label><input type="checkbox" data-perm="${k}" ${curPerms.includes(k) ? 'checked' : ''}/> ${lbl}</label>`)
2357
+ .join('');
2275
2358
  const numRows = (b.numbers || [])
2276
2359
  .map((n) => `<div class="bot-num-row" data-num="${n.num}"><span class="n">+${escapeHtml(n.num)}${n.name ? ' · ' + escapeHtml(n.name) : ''}</span><span class="x" title="${_t('bot_num_del')}">×</span></div>`)
2277
2360
  .join('');
@@ -2293,12 +2376,8 @@ function renderBotSettings(pane, b) {
2293
2376
  <div class="bot-checks" id="bSee">${seeChecks}</div>
2294
2377
  ${b.admin ? '' : `
2295
2378
  <label class="mem-label">${_t('bot_perm')}</label>
2296
- <select id="bPerm" class="inp">
2297
- <option value="all" ${b.perm === 'all' ? 'selected' : ''}>${_t('wa_perm_all')}</option>
2298
- <option value="web" ${b.perm === 'web' ? 'selected' : ''}>web</option>
2299
- <option value="read" ${b.perm === 'read' ? 'selected' : ''}>${_t('wa_perm_read')}</option>
2300
- <option value="chat" ${b.perm === 'chat' ? 'selected' : ''}>${_t('wa_perm_chat')}</option>
2301
- </select>
2379
+ <div class="bot-checks" id="bPerm">${permChecks}</div>
2380
+ <div class="sub" style="margin-top:4px">${_t('bot_perm_note')}</div>
2302
2381
  <label class="mem-label">${_t('bot_skills')}</label>
2303
2382
  <div class="bot-checks" id="bSkills">${skillChecks}</div>`}
2304
2383
  <label class="mem-label">${_t('bot_browser')}</label>
@@ -2346,6 +2425,18 @@ function renderBotSettings(pane, b) {
2346
2425
  if (bb) renderBotSettings(pane, bb);
2347
2426
  })
2348
2427
  );
2428
+ /* 'all' hariç diğer izinler çoklu seçilebilir; 'all' işaretlenince diğerleri kapanır */
2429
+ f.querySelectorAll('#bPerm input').forEach((c) =>
2430
+ c.addEventListener('change', () => {
2431
+ if (c.dataset.perm === 'all' && c.checked) {
2432
+ f.querySelectorAll('#bPerm input').forEach((x) => { if (x !== c) x.checked = false; });
2433
+ } else if (c.checked) {
2434
+ const all = f.querySelector('#bPerm input[data-perm="all"]');
2435
+ if (all) all.checked = false;
2436
+ }
2437
+ if (![...f.querySelectorAll('#bPerm input')].some((x) => x.checked)) c.checked = true; // en az biri seçili kalsın
2438
+ })
2439
+ );
2349
2440
  $('#bSave').addEventListener('click', async () => {
2350
2441
  const patch = {
2351
2442
  name: $('#bName').value.trim(),
@@ -2358,7 +2449,8 @@ function renderBotSettings(pane, b) {
2358
2449
  numbers: (b.numbers || []).map((n) => n.num),
2359
2450
  };
2360
2451
  if (!b.admin) {
2361
- patch.perm = $('#bPerm').value;
2452
+ const selPerms = [...f.querySelectorAll('#bPerm input:checked')].map((c) => c.dataset.perm);
2453
+ patch.perm = selPerms.includes('all') ? 'all' : selPerms;
2362
2454
  const sk = {};
2363
2455
  f.querySelectorAll('#bSkills input').forEach((c) => { sk[c.dataset.skill] = c.checked; });
2364
2456
  patch.skills = sk;
@@ -2784,6 +2876,12 @@ function renderAgentsPane() {
2784
2876
  function onEvent(ev) {
2785
2877
  if (ev.type === 'sessions') { refreshSessions(); return; }
2786
2878
  if (ev.type === 'approval') {
2879
+ /* Beast Code oturumunun onayı: panelde ipucu + kart sohbete düşer */
2880
+ if (bcSessionId && ev.sessionId === bcSessionId) {
2881
+ showApprovalCard(ev);
2882
+ bcLine('t-dim', '⏳ onay bekleniyor: ' + (ev.tool || '?') + ' — IDE modundan çıkıp sohbetteki kartı onayla');
2883
+ return;
2884
+ }
2787
2885
  if (ev.sessionId && ev.sessionId !== activeId) return;
2788
2886
  showApprovalCard(ev);
2789
2887
  return;
@@ -2800,6 +2898,13 @@ function onEvent(ev) {
2800
2898
  scheduleAgentsRender();
2801
2899
  return;
2802
2900
  }
2901
+ /* Beast Code oturumu (IDE modu ortasındaki panel): olayları panele akıt,
2902
+ ana sohbeti kirletme */
2903
+ if (bcSessionId && ev.sessionId === bcSessionId) {
2904
+ bcIngest(ev);
2905
+ return;
2906
+ }
2907
+
2803
2908
  if (ev.sessionId && ev.sessionId !== activeId) {
2804
2909
  /* paralel ajan canlı akışı: sohbeti kirletme, sekmede göster */
2805
2910
  if (agentState.runningIds.has(ev.sessionId)) {
@@ -2816,6 +2921,7 @@ function onEvent(ev) {
2816
2921
  els.msgs.innerHTML = '';
2817
2922
  streamEl = null;
2818
2923
  streamRaw = '';
2924
+ closeChatToolGroup();
2819
2925
  showEmpty(true);
2820
2926
  refreshSessions();
2821
2927
  break;
@@ -2825,7 +2931,12 @@ function onEvent(ev) {
2825
2931
  break;
2826
2932
  case 'message':
2827
2933
  if (ev.message.role === 'user') addUserBubble(ev.message.content);
2828
- else if (ev.message.role === 'assistant') finalizeAssistant(ev.message.content);
2934
+ else if (ev.message.role === 'assistant') {
2935
+ finalizeAssistant(ev.message.content);
2936
+ /* tool_calls'lı iş turu grupları açık tutar; saf metin (normal cevap) kapatır */
2937
+ const hasTools = Array.isArray(ev.message.tool_calls) && ev.message.tool_calls.length > 0;
2938
+ if (!hasTools) closeChatToolGroup();
2939
+ }
2829
2940
  break;
2830
2941
  case 'token':
2831
2942
  ensureStreamBubble();
@@ -2869,6 +2980,9 @@ function onEvent(ev) {
2869
2980
  } else {
2870
2981
  toggleRail(!railPrefBeforeBrowser);
2871
2982
  }
2983
+ /* IDE modunda tarayıcı açılır/kapanır/genişlik değişirse row yeniden bölünür —
2984
+ editör + Beast Code ORTAK kırpılıp ORTAK açılır (kullanıcı payı korunur) */
2985
+ if (ideModeOn()) ideSplitApplyFrac();
2872
2986
  if (shown) {
2873
2987
  els.browserBtn.classList.add('on');
2874
2988
  if (ev.url && document.activeElement !== els.bbUrl) {
@@ -2894,11 +3008,13 @@ function onEvent(ev) {
2894
3008
  applyState();
2895
3009
  break;
2896
3010
  case 'done':
3011
+ closeChatToolGroup();
2897
3012
  setBusy(false);
2898
3013
  refreshSessions();
2899
3014
  els.input.focus();
2900
3015
  break;
2901
3016
  case 'error':
3017
+ closeChatToolGroup();
2902
3018
  setBusy(false);
2903
3019
  addErrorBubble(ev.error);
2904
3020
  refreshSessions();
@@ -4315,3 +4431,732 @@ document.querySelectorAll('.stab').forEach((b) =>
4315
4431
  renderStorePane();
4316
4432
  })
4317
4433
  );
4434
+
4435
+
4436
+ /* ================= IDE MODU =================
4437
+ Tek tuş (#ideBtn): sol → dosya gezgini, orta → chat, sağ → dahili tarayıcı (preview).
4438
+ Preview: workspace kökündeki index.html (yoksa ilk *.html) sağdaki tarayıcıda açılır.
4439
+ Dosyaya tıkla → editör; klasöre tıkla → aç/kapa. */
4440
+
4441
+ const IDE_ICONS = {
4442
+ '.html': '🌐', '.htm': '🌐', '.css': '🎨', '.js': '📜', '.mjs': '📜', '.cjs': '📜',
4443
+ '.ts': '📜', '.jsx': '📜', '.tsx': '📜', '.json': '🧾', '.md': '📝', '.txt': '📄',
4444
+ '.py': '🐍', '.ps1': '⚙️', '.bat': '⚙️', '.cmd': '⚙️', '.sh': '⚙️', '.yaml': '🧾',
4445
+ '.yml': '🧾', '.png': '🖼️', '.jpg': '🖼️', '.jpeg': '🖼️', '.gif': '🖼️', '.svg': '🖼️',
4446
+ '.pdf': '📕',
4447
+ };
4448
+
4449
+ const ideState = {
4450
+ cache: new Map(), // rel -> entries[]
4451
+ open: new Set(['']),
4452
+ path: '',
4453
+ };
4454
+
4455
+ function ideModeOn() {
4456
+ return document.body.classList.contains('ide-mode');
4457
+ }
4458
+
4459
+ async function setIdeMode(on) {
4460
+ document.body.classList.toggle('ide-mode', !!on);
4461
+ /* IDE modu oturumluk: uygulama HER AÇILIŞTA agent (chat) modunda başlar */
4462
+ toggleRail(!!on);
4463
+ /* IDE'den çıkış: açık tarayıcı da kapansın — her şey default haline dönsün */
4464
+ if (!on && document.body.classList.contains('browser-open')) {
4465
+ try { beast.toggleBrowser(); } catch {}
4466
+ }
4467
+ if (on) {
4468
+ ideSplitRestore();
4469
+ await loadIdeTree();
4470
+ bcBanner();
4471
+ }
4472
+ }
4473
+
4474
+ /* ---------- editor/chat ayırıcı (VS Code gibi sürükle-bırak) ----------
4475
+ Orantılı model: kaydedilen, Beast Code'un ideRow içindeki PAYI'dır (0-1).
4476
+ Tarayıcı açılınca/kapanınca/genişlik değişince row yeniden bölünür — editör
4477
+ ve Beast Code ORTAK kırpılıp ORTAK açılır (oranlar korunur).
4478
+ Kayıt yoksa default: tarayıcı açıkken 3 eşit parça, kapalıyken 2 eşit parça. */
4479
+ let ideSplitFrac = 0; /* 0 = kayıt yok → 0.5 default */
4480
+
4481
+ function ideSplitApplyFrac() {
4482
+ if (!ideSplitFrac) ideSplitFrac = 0.5;
4483
+ ideSplitFrac = Math.max(0.2, Math.min(ideSplitFrac, 0.8));
4484
+ const rowW = els.ideRow && els.ideRow.clientWidth ? els.ideRow.clientWidth : window.innerWidth - 250;
4485
+ const w = Math.max(280, Math.min(Math.round(rowW * ideSplitFrac), Math.max(320, rowW - 300)));
4486
+ document.body.style.setProperty('--ideSplit', w + 'px');
4487
+ }
4488
+
4489
+ function ideSplitSave() {
4490
+ try { localStorage.setItem('beast.ideSplit', String(ideSplitFrac)); } catch {}
4491
+ }
4492
+
4493
+ function ideSplitRestore() {
4494
+ let saved = 0;
4495
+ try { saved = parseFloat(localStorage.getItem('beast.ideSplit')) || 0; } catch {}
4496
+ const hasUserSetting = saved > 0.05 && saved < 0.95;
4497
+ ideSplitFrac = hasUserSetting ? saved : 0;
4498
+ ideSplitApplyFrac();
4499
+ /* tarayıcı açıksa ve kullanıcı hiç ayar yapmadıysa üç parçayı eşitle */
4500
+ const browserOpen = document.body.classList.contains('browser-open');
4501
+ if (browserOpen && !hasUserSetting) {
4502
+ beast.browserSetWidth(Math.round((window.innerWidth - 250) / 3)).catch(() => {});
4503
+ }
4504
+ }
4505
+
4506
+ if (els.ideSplit) {
4507
+ els.ideSplit.addEventListener('mousedown', (e) => {
4508
+ e.preventDefault();
4509
+ els.ideSplit.classList.add('dragging');
4510
+ /* tarayıcı dock'u drag sırasında sabit — sağ kenar referansı mousedown anında */
4511
+ const rightEdge = els.bcPanel ? els.bcPanel.getBoundingClientRect().right : window.innerWidth;
4512
+ const rowW = Math.max(600, els.ideRow && els.ideRow.clientWidth ? els.ideRow.clientWidth : window.innerWidth - 250);
4513
+ const move = (ev) => {
4514
+ const w = Math.max(280, Math.min(rightEdge - ev.clientX, rowW - 300));
4515
+ ideSplitFrac = Math.max(0.2, Math.min(w / rowW, 0.8));
4516
+ document.body.style.setProperty('--ideSplit', Math.round(w) + 'px');
4517
+ };
4518
+ const up = () => {
4519
+ document.removeEventListener('mousemove', move);
4520
+ document.removeEventListener('mouseup', up);
4521
+ els.ideSplit.classList.remove('dragging');
4522
+ ideSplitSave();
4523
+ };
4524
+ document.addEventListener('mousemove', move);
4525
+ document.addEventListener('mouseup', up);
4526
+ });
4527
+ window.addEventListener('resize', () => {
4528
+ if (ideModeOn()) ideSplitApplyFrac();
4529
+ });
4530
+ }
4531
+
4532
+ async function loadIdeTree() {
4533
+ ideState.cache.clear();
4534
+ await renderFileTree();
4535
+ }
4536
+
4537
+ function fmtFileSize(n) {
4538
+ n = Number(n) || 0;
4539
+ if (n >= 1048576) return (n / 1048576).toFixed(1) + 'M';
4540
+ if (n >= 1024) return Math.round(n / 1024) + 'K';
4541
+ return n ? n + 'B' : '';
4542
+ }
4543
+
4544
+ async function renderFileTree() {
4545
+ const tree = $('#fileTree');
4546
+ if (!tree) return;
4547
+ tree.innerHTML = '';
4548
+ const wsLabel = $('#filePanelPath');
4549
+ const build = async (rel, depth, container) => {
4550
+ let entries = ideState.cache.get(rel);
4551
+ if (!entries) {
4552
+ const r = await beast.ideTree(rel).catch(() => null);
4553
+ if (!r || !r.ok) {
4554
+ if (!rel) container.innerHTML = `<div class="file-empty">${escapeHtml((r && r.error) || 'okunamadı')}</div>`;
4555
+ return;
4556
+ }
4557
+ entries = r.entries;
4558
+ ideState.cache.set(rel, entries);
4559
+ if (r.workspace) {
4560
+ ideState.path = r.workspace;
4561
+ if (wsLabel) { wsLabel.textContent = r.workspace; wsLabel.title = r.workspace; }
4562
+ if (els.bcCwd) { els.bcCwd.textContent = r.workspace; els.bcCwd.title = r.workspace; }
4563
+ bcOnFolderChanged(r.workspace);
4564
+ }
4565
+ }
4566
+ for (const e of entries) {
4567
+ const child = rel ? rel + '/' + e.name : e.name;
4568
+ const row = document.createElement('div');
4569
+ row.className = 'file-row';
4570
+ row.style.paddingLeft = 6 + depth * 14 + 'px';
4571
+ const ico = e.dir ? (ideState.open.has(child) ? '📂' : '📁') : IDE_ICONS[e.name.slice(e.name.lastIndexOf('.')).toLowerCase()] || '📄';
4572
+ row.innerHTML =
4573
+ `<span class="f-ico">${ico}</span>` +
4574
+ `<span class="f-nm" title="${escapeHtml(e.name)}">${escapeHtml(e.name)}</span>` +
4575
+ (e.dir ? '' : `<span class="f-sz">${fmtFileSize(e.size)}</span>`);
4576
+ row.addEventListener('click', () => {
4577
+ if (e.dir) {
4578
+ if (ideState.open.has(child)) ideState.open.delete(child);
4579
+ else ideState.open.add(child);
4580
+ renderFileTree();
4581
+ } else {
4582
+ codeOpen(child);
4583
+ }
4584
+ });
4585
+ row.dataset.rel = child;
4586
+ row.dataset.dir = e.dir ? '1' : '0';
4587
+ container.appendChild(row);
4588
+ if (e.dir && ideState.open.has(child)) {
4589
+ const box = document.createElement('div');
4590
+ container.appendChild(box);
4591
+ await build(child, depth + 1, box);
4592
+ }
4593
+ }
4594
+ };
4595
+ await build('', 0, tree);
4596
+ }
4597
+
4598
+ /* ---------- sekmeli kod editörü (VS Code düzeni) ----------
4599
+ Dosya tıklaması MODAL değil, codePane içinde SEKME açar.
4600
+ Yanında Beast Code chat'i, onun sağındaki preview tarayıcı durur. */
4601
+
4602
+ const codeTabs = []; /* { rel, content, dirty } */
4603
+ let codeActive = -1;
4604
+
4605
+ function codeRenderTabs() {
4606
+ const bar = els.codeTabs;
4607
+ if (!bar) return;
4608
+ bar.innerHTML = '';
4609
+ codeTabs.forEach((t, i) => {
4610
+ const el = document.createElement('div');
4611
+ el.className = 'code-tab' + (i === codeActive ? ' active' : '') + (t.dirty ? ' dirty' : '');
4612
+ el.title = t.rel;
4613
+ el.innerHTML =
4614
+ '<span class="code-tab-name"></span>' +
4615
+ '<button class="code-tab-x" title="Sekmeyi kapat">\u00D7</button>';
4616
+ el.querySelector('.code-tab-name').textContent = t.rel;
4617
+ el.addEventListener('click', () => codeActivate(i));
4618
+ el.querySelector('.code-tab-x').addEventListener('click', (e) => {
4619
+ e.stopPropagation();
4620
+ codeClose(i);
4621
+ });
4622
+ bar.appendChild(el);
4623
+ });
4624
+ }
4625
+
4626
+ function codeFlushActive() {
4627
+ if (codeActive >= 0 && codeTabs[codeActive]) {
4628
+ codeTabs[codeActive].content = els.codeTa.value;
4629
+ }
4630
+ }
4631
+
4632
+ function codeActivate(i) {
4633
+ codeFlushActive();
4634
+ if (i < 0 || i >= codeTabs.length) {
4635
+ codeActive = -1;
4636
+ els.codeTa.value = '';
4637
+ els.codePath.textContent = '\u2014';
4638
+ codeRenderTabs();
4639
+ return;
4640
+ }
4641
+ codeActive = i;
4642
+ const t = codeTabs[i];
4643
+ els.codeTa.value = t.content;
4644
+ els.codePath.textContent = t.rel;
4645
+ els.codePath.title = (ideState.path || '') + '\\' + t.rel.replace(/\//g, '\\');
4646
+ codeRenderTabs();
4647
+ }
4648
+
4649
+ async function codeOpen(rel) {
4650
+ const idx = codeTabs.findIndex((t) => t.rel === rel);
4651
+ if (idx >= 0) {
4652
+ codeActivate(idx);
4653
+ return;
4654
+ }
4655
+ const r = await beast.ideRead(rel).catch(() => null);
4656
+ if (!r || !r.ok) {
4657
+ toast((r && r.error) || 'okunamad\u0131');
4658
+ return;
4659
+ }
4660
+ codeTabs.push({ rel, content: r.content || '', dirty: false });
4661
+ codeActivate(codeTabs.length - 1);
4662
+ els.codeTa.focus();
4663
+ }
4664
+
4665
+ function codeClose(i) {
4666
+ if (i < 0 || i >= codeTabs.length) return;
4667
+ codeFlushActive();
4668
+ const oldActive = codeActive;
4669
+ const wasActive = i === oldActive;
4670
+ codeTabs.splice(i, 1);
4671
+ if (codeTabs.length === 0) {
4672
+ codeActive = -1;
4673
+ els.codeTa.value = '';
4674
+ els.codePath.textContent = '\u2014';
4675
+ codeRenderTabs();
4676
+ return;
4677
+ }
4678
+ let next = oldActive;
4679
+ if (wasActive) next = Math.min(i, codeTabs.length - 1);
4680
+ else if (i < oldActive) next = oldActive - 1;
4681
+ codeActive = -1; /* activate içindeki flush, eski içerikle yeni sekmeyi ezmesin */
4682
+ codeActivate(next);
4683
+ }
4684
+
4685
+ async function codeSave() {
4686
+ if (codeActive < 0 || !codeTabs[codeActive]) return;
4687
+ codeFlushActive();
4688
+ const t = codeTabs[codeActive];
4689
+ const r = await beast.ideWrite(t.rel, t.content).catch(() => null);
4690
+ if (r && r.ok) {
4691
+ t.dirty = false;
4692
+ codeRenderTabs();
4693
+ toast(_t('ide_saved'));
4694
+ } else {
4695
+ toast((r && r.error) || 'kaydedilemedi');
4696
+ }
4697
+ }
4698
+
4699
+ $('#ideBtn').addEventListener('click', () => setIdeMode(!ideModeOn()));
4700
+ $('#filePick').addEventListener('click', async () => {
4701
+ const r = await beast.ideSetRoot().catch(() => null);
4702
+ if (r && r.ok) {
4703
+ ideState.open = new Set(['']);
4704
+ await loadIdeTree();
4705
+ } else if (r && !r.canceled && r.error) {
4706
+ toast(r.error);
4707
+ }
4708
+ });
4709
+ $('#fileRefresh').addEventListener('click', () => loadIdeTree());
4710
+ $('#filePreview').addEventListener('click', async () => {
4711
+ const r = await beast.idePreview().catch(() => null);
4712
+ if (r && r.ok) {
4713
+ if (ideModeOn() === false) setIdeMode(true);
4714
+ } else {
4715
+ toast((r && r.error) || 'preview açılamadı');
4716
+ }
4717
+ });
4718
+ if (els.codeSave) els.codeSave.addEventListener('click', codeSave);
4719
+ if (els.codeTa) {
4720
+ els.codeTa.addEventListener('input', () => {
4721
+ if (codeActive < 0) return;
4722
+ codeTabs[codeActive].dirty = true;
4723
+ const el = els.codeTabs.children[codeActive];
4724
+ if (el) el.classList.add('dirty');
4725
+ });
4726
+ els.codeTa.addEventListener('keydown', (e) => {
4727
+ if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 's') {
4728
+ e.preventDefault();
4729
+ codeSave();
4730
+ }
4731
+ });
4732
+ }
4733
+
4734
+ /* ---------- dosya ağacı sağ tık menüsü (Sil / Aç / Preview) ---------- */
4735
+
4736
+ function fileCtxHide() {
4737
+ if (els.fileCtxMenu) els.fileCtxMenu.hidden = true;
4738
+ }
4739
+
4740
+ function fileCtxShow(e, rel, isDir) {
4741
+ const menu = els.fileCtxMenu;
4742
+ if (!menu || !rel) return;
4743
+ e.preventDefault();
4744
+ const isHtml = /\.html?$/i.test(rel);
4745
+ const items = [];
4746
+ if (isDir) {
4747
+ items.push({
4748
+ label: ideState.open.has(rel) ? 'Kapat' : 'Aç',
4749
+ fn: () => {
4750
+ if (ideState.open.has(rel)) ideState.open.delete(rel);
4751
+ else ideState.open.add(rel);
4752
+ renderFileTree();
4753
+ },
4754
+ });
4755
+ } else {
4756
+ items.push({ label: 'Aç', fn: () => codeOpen(rel) });
4757
+ if (isHtml) {
4758
+ items.push({
4759
+ label: 'Preview',
4760
+ fn: async () => {
4761
+ const r = await beast.idePreviewFile(rel).catch(() => null);
4762
+ if (!r || !r.ok) toast((r && r.error) || 'preview açılamadı');
4763
+ },
4764
+ });
4765
+ }
4766
+ }
4767
+ items.push({
4768
+ label: 'Sil',
4769
+ danger: true,
4770
+ fn: async () => {
4771
+ const r = await beast.ideDelete(rel).catch(() => null);
4772
+ if (r && r.ok) {
4773
+ /* silinen dosya/klasörün açık sekmelerini kapat */
4774
+ for (let i = codeTabs.length - 1; i >= 0; i--) {
4775
+ if (codeTabs[i].rel === rel || codeTabs[i].rel.startsWith(rel + '/')) codeClose(i);
4776
+ }
4777
+ await loadIdeTree();
4778
+ } else if (r && !r.canceled && r.error) {
4779
+ toast(r.error);
4780
+ }
4781
+ },
4782
+ });
4783
+
4784
+ menu.innerHTML = '';
4785
+ for (const it of items) {
4786
+ const el = document.createElement('div');
4787
+ el.className = 'ctx-item' + (it.danger ? ' danger' : '');
4788
+ el.textContent = it.label;
4789
+ el.addEventListener('click', () => {
4790
+ fileCtxHide();
4791
+ it.fn();
4792
+ });
4793
+ menu.appendChild(el);
4794
+ }
4795
+ menu.hidden = false;
4796
+ const mh = items.length * 33 + 8;
4797
+ menu.style.left = Math.max(4, Math.min(e.clientX, window.innerWidth - 170)) + 'px';
4798
+ menu.style.top = Math.max(4, Math.min(e.clientY, window.innerHeight - mh)) + 'px';
4799
+ }
4800
+
4801
+ if (els.fileCtxMenu) {
4802
+ document.addEventListener('click', fileCtxHide);
4803
+ window.addEventListener('blur', fileCtxHide);
4804
+ window.addEventListener('keydown', (e) => {
4805
+ if (e.key === 'Escape') fileCtxHide();
4806
+ });
4807
+ }
4808
+ $('#fileTree').addEventListener('contextmenu', (e) => {
4809
+ const row = e.target.closest('.file-row');
4810
+ if (!row || !row.dataset.rel) {
4811
+ fileCtxHide();
4812
+ return;
4813
+ }
4814
+ fileCtxShow(e, row.dataset.rel, row.dataset.dir === '1');
4815
+ });
4816
+
4817
+ /* ---------- IDE modu ortası: BEAST CODE paneli ----------
4818
+ Sohbet yerine Beast'in kendi ajanı (varsayılan model) çalışır;
4819
+ soldaki dosya panelindeki klasörü workspace olarak kullanır.
4820
+ Gizli engine oturumu → olaylar onEvent içinde panele yönlendirilir. */
4821
+
4822
+ const BC_MAX_LINES = 1200;
4823
+ let bcSessionId = null;
4824
+ let bcRunning = false;
4825
+ let bcBannerDone = false;
4826
+ let bcStreamEl = null;
4827
+ let bcStreamRaw = '';
4828
+
4829
+ function bcScroll(force) {
4830
+ const b = els.bcOut;
4831
+ if (!b) return;
4832
+ /* her yeni içerikte son mesaja doğru otomatik kaydır */
4833
+ b.scrollTop = b.scrollHeight;
4834
+ }
4835
+
4836
+ /* kutu içi gövdeyi (bc-toolbody) de son bölüme kaydır */
4837
+ function bcBodyScroll(el) {
4838
+ const b = el && el.closest ? el.closest('.bc-toolbody') : null;
4839
+ if (b) b.scrollTop = b.scrollHeight;
4840
+ }
4841
+
4842
+ function bcLine(cls, text, time = false) {
4843
+ if (!els.bcOut) return;
4844
+ const el = document.createElement('div');
4845
+ el.className = 't-line ' + (cls || '');
4846
+ const ts = time ? '<span class="t-time">' + new Date().toTimeString().slice(0, 8) + '</span> ' : '';
4847
+ el.innerHTML = ts + escapeHtml(String(text ?? ''));
4848
+ els.bcOut.appendChild(el);
4849
+ while (els.bcOut.childElementCount > BC_MAX_LINES) els.bcOut.removeChild(els.bcOut.firstChild);
4850
+ bcScroll();
4851
+ }
4852
+
4853
+ function bcBanner() {
4854
+ if (bcBannerDone) return;
4855
+ bcBannerDone = true;
4856
+ const cwd = ideState.path || '';
4857
+ if (els.bcCwd && cwd) { els.bcCwd.textContent = cwd; els.bcCwd.title = cwd; }
4858
+ bcLine('t-sys', 'BEAST CODE — çalışma klasörü: ' + (cwd || '?'));
4859
+ 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);
4860
+ }
4861
+
4862
+ /* Klasör değişimi (Klasör seç / kök değişti) → panel taze başlar;
4863
+ oturumlar main tarafında klasör bazlı tutulur (klasöre dönüşte aynı sohbet sürer).
4864
+ bcSessionId kasıtlı sıfırlanmaz: klasör değişse bile çalışan işin done/idle
4865
+ sinyali panele ulaşıp kilidi açar. */
4866
+ let bcWsPath = '';
4867
+
4868
+ function bcOnFolderChanged(p) {
4869
+ if (!p || bcWsPath === p) return;
4870
+ const first = !bcWsPath;
4871
+ bcWsPath = p;
4872
+ if (first) return; /* ilk yükleme — panel zaten boş, banner setIdeMode'da basılır */
4873
+ if (els.bcOut) els.bcOut.innerHTML = '';
4874
+ bcTools.clear();
4875
+ bcCloseToolGroup();
4876
+ bcHideTodos();
4877
+ bcStreamEl = null;
4878
+ bcStreamRaw = '';
4879
+ bcBannerDone = false;
4880
+ bcBanner();
4881
+ }
4882
+
4883
+ function bcSetBusy(v) {
4884
+ bcRunning = !!v;
4885
+ if (els.bcStop) els.bcStop.hidden = !v;
4886
+ }
4887
+
4888
+ function bcFlushStream() {
4889
+ bcStreamEl = null;
4890
+ bcStreamRaw = '';
4891
+ }
4892
+
4893
+ function bcStreamDelta(delta) {
4894
+ if (!bcStreamEl) {
4895
+ bcStreamEl = document.createElement('div');
4896
+ bcStreamEl.className = 't-line t-out';
4897
+ els.bcOut.appendChild(bcStreamEl);
4898
+ }
4899
+ bcStreamRaw += String(delta || '');
4900
+ bcStreamEl.textContent = bcStreamRaw.slice(-4000);
4901
+ bcScroll(true);
4902
+ }
4903
+
4904
+ /* İş bitti → preview açık ve IDE modundaysa workspace sayfasını otomatik yenile */
4905
+ let bcPrevAt = 0;
4906
+
4907
+ function bcRefreshPreview() {
4908
+ const now = Date.now();
4909
+ if (now - bcPrevAt < 1500) return; /* done + idle çift tetiklemesin */
4910
+ bcPrevAt = now;
4911
+ if (!ideModeOn() || !document.body.classList.contains('browser-open')) return;
4912
+ beast.idePreview().catch(() => {});
4913
+ }
4914
+
4915
+ /* ---------- Beast Code araç kutuları ----------
4916
+ Ard arda gelen araç çağrıları TEK kutuda toplanır. Kural: engine mesajı
4917
+ tool_calls İÇERİYORSA (iş turu) grup AÇIK kalır — aradaki ara yazılar kutu
4918
+ içine not olarak akar; tool_calls İÇERMEYEN saf metin (normal mesaj) gelirse
4919
+ grup kapanır, sonraki çağrılar YENİ kutu açar.
4920
+ Kutu çalışırken etrafında fosforlu açık mavi outline döner; her çağrı kutu
4921
+ içinde bir bölüm, çıktı max 180px scroll'lu. */
4922
+ const bcTools = new Map(); /* callId → { box, sec } */
4923
+ let bcCurBox = null; /* açık araç grubu — ard arda çağrılar buraya eklenir */
4924
+ let bcNoteEl = null; /* grup içindeki ara yazı notu */
4925
+ let bcNoteRaw = '';
4926
+
4927
+ function bcNoteDetach() {
4928
+ bcNoteEl = null;
4929
+ bcNoteRaw = '';
4930
+ }
4931
+
4932
+ function bcCloseToolGroup() {
4933
+ /* grup kapanınca (normal mesaj / iş bitti) ring durur */
4934
+ if (bcCurBox) bcCurBox.classList.remove('running');
4935
+ bcCurBox = null;
4936
+ bcNoteDetach();
4937
+ }
4938
+
4939
+ function bcToolGroupNew() {
4940
+ const box = document.createElement('div');
4941
+ box.className = 'bc-toolbox';
4942
+ const body = document.createElement('div');
4943
+ body.className = 'bc-toolbody';
4944
+ box.appendChild(body);
4945
+ els.bcOut.appendChild(box);
4946
+ while (els.bcOut.childElementCount > BC_MAX_LINES) els.bcOut.removeChild(els.bcOut.firstChild);
4947
+ return box;
4948
+ }
4949
+
4950
+ function bcToolBoxStart(callId, name, args) {
4951
+ bcFlushStream();
4952
+ if (!bcCurBox) bcCurBox = bcToolGroupNew();
4953
+ const sec = document.createElement('div');
4954
+ sec.className = 'bc-toolsection';
4955
+ const short = termShortTool(name, args);
4956
+ sec.innerHTML =
4957
+ '<div class="bc-toolhead">' +
4958
+ '<span class="bc-toolspin"></span>' +
4959
+ '<span class="bc-toolname">▸ ' + escapeHtml(String(name || 'araç')) + '</span>' +
4960
+ (short ? '<span class="bc-toolar" title="' + escapeHtml(short) + '">' + escapeHtml(short) + '</span>' : '') +
4961
+ '</div>' +
4962
+ '<div class="bc-toolout" hidden></div>';
4963
+ /* ring: iş bitene / grup kapanana kadar bu kutuda dönmeye devam eder */
4964
+ bcCurBox.classList.add('running');
4965
+ const body = bcCurBox.querySelector('.bc-toolbody') || bcCurBox;
4966
+ body.appendChild(sec);
4967
+ if (callId) bcTools.set(callId, { box: bcCurBox, sec });
4968
+ bcScroll();
4969
+ bcBodyScroll(body);
4970
+ }
4971
+
4972
+ function bcToolBoxEnd(callId, ok, result) {
4973
+ bcFlushStream();
4974
+ const t = (callId && bcTools.get(callId)) || null;
4975
+ if (callId) bcTools.delete(callId);
4976
+ const out = String(result || '').replace(/\s+$/, '');
4977
+ if (t) {
4978
+ const { sec } = t;
4979
+ /* ring burada SÖNDÜRÜLMEZ — iş bitene kadar (bcCloseToolGroup) döner */
4980
+ sec.classList.add('done');
4981
+ if (!ok) sec.classList.add('failed');
4982
+ const pre = sec.querySelector('.bc-toolout');
4983
+ if (out) {
4984
+ pre.hidden = false;
4985
+ pre.textContent = out.length > 4000 ? out.slice(0, 4000) + '\n… (kesildi)' : out;
4986
+ } else {
4987
+ pre.remove();
4988
+ }
4989
+ bcBodyScroll(sec);
4990
+ } else if (out || !ok) {
4991
+ /* başlangıcı kaçmış çağrı — düz satır olarak düş */
4992
+ bcLine(ok ? 't-out' : 't-err', out ? (out.length > 400 ? out.slice(0, 400) + ' …(kesildi)' : out) : '(hata)');
4993
+ }
4994
+ bcScroll();
4995
+ }
4996
+
4997
+ /* ---------- Beast Code todo kartı ----------
4998
+ Inputun (chat) hemen üstünde ortalı kompakt kutu; #bcTodoWrap içinde yaşar,
4999
+ scroll'lu çıktı akışını kirletmez. */
5000
+ function bcRenderTodos(todos) {
5001
+ const list = Array.isArray(todos) ? todos : [];
5002
+ if (!els.bcTodoWrap) return;
5003
+ bcFlushStream();
5004
+ if (!list.length) {
5005
+ bcHideTodos();
5006
+ return;
5007
+ }
5008
+ els.bcTodoWrap.hidden = false;
5009
+ els.bcTodoWrap.innerHTML =
5010
+ '<div class="bc-todobox">' +
5011
+ '<div class="bc-todotitle"><span>GÖREVLER</span><span class="bc-todocount"></span></div>' +
5012
+ '<div class="bc-todoitems"></div>';
5013
+ const done = list.filter((t) => t.status === 'done').length;
5014
+ els.bcTodoWrap.querySelector('.bc-todocount').textContent = done + '/' + list.length;
5015
+ const wrap = els.bcTodoWrap.querySelector('.bc-todoitems');
5016
+ wrap.innerHTML = '';
5017
+ for (const t of list) {
5018
+ const st = TODO_GLYPH[t.status] ? t.status : 'pending';
5019
+ const row = document.createElement('div');
5020
+ row.className = 'bc-todoitem ' + st;
5021
+ row.innerHTML =
5022
+ '<span class="bc-todocheck">' + TODO_GLYPH[st] + '</span>' +
5023
+ '<span class="bc-todotext"></span>';
5024
+ row.querySelector('.bc-todotext').textContent = String(t.title || '');
5025
+ wrap.appendChild(row);
5026
+ }
5027
+ }
5028
+
5029
+ function bcHideTodos() {
5030
+ if (els.bcTodoWrap) {
5031
+ els.bcTodoWrap.hidden = true;
5032
+ els.bcTodoWrap.innerHTML = '';
5033
+ }
5034
+ }
5035
+
5036
+ /* Ara yazı → açık grup varsa kutu İÇİNE not olarak akar (kronoloji korunur) */
5037
+ function bcNoteStream(delta) {
5038
+ if (!bcCurBox) return false;
5039
+ if (!bcNoteEl) {
5040
+ bcNoteEl = document.createElement('div');
5041
+ bcNoteEl.className = 'bc-toolnote';
5042
+ const body = bcCurBox.querySelector('.bc-toolbody') || bcCurBox;
5043
+ body.appendChild(bcNoteEl);
5044
+ }
5045
+ bcNoteRaw += String(delta || '');
5046
+ bcNoteEl.textContent = bcNoteRaw.slice(-2000);
5047
+ bcScroll(true);
5048
+ bcBodyScroll(bcNoteEl);
5049
+ return true;
5050
+ }
5051
+
5052
+ /* Beast Code oturumunun engine olayları → panel çıktısı */
5053
+ function bcIngest(ev) {
5054
+ switch (ev.type) {
5055
+ case 'token':
5056
+ /* grup açıksa ara not (kutu içi); değilse normal akış */
5057
+ if (bcNoteStream(ev.delta)) break;
5058
+ bcStreamDelta(ev.delta);
5059
+ break;
5060
+ case 'message':
5061
+ if (ev.message && ev.message.role === 'assistant') {
5062
+ const content = typeof ev.message.content === 'string' ? ev.message.content : '';
5063
+ const hasTools = Array.isArray(ev.message.tool_calls) && ev.message.tool_calls.length > 0;
5064
+ if (hasTools) {
5065
+ /* iş turu: grup AÇIK kalır — ard arda araçlar aynı kutuda sürer.
5066
+ Kutu içine akmış ara not kalıcı olur, işaretçileri bırak */
5067
+ bcNoteDetach();
5068
+ bcFlushStream();
5069
+ break;
5070
+ }
5071
+ /* NORMAL MESAJ: kutu içine düşen taslak notu çıkar, cevabı düz metin bas */
5072
+ if (bcNoteEl) { bcNoteEl.remove(); bcNoteDetach(); }
5073
+ if (content && !bcStreamRaw.trim()) bcLine('t-out', content);
5074
+ bcCloseToolGroup();
5075
+ bcFlushStream();
5076
+ }
5077
+ break;
5078
+ case 'todos':
5079
+ bcRenderTodos(ev.todos);
5080
+ break;
5081
+ case 'tool-start':
5082
+ bcToolBoxStart(ev.callId, ev.name, ev.args);
5083
+ break;
5084
+ case 'tool-end':
5085
+ bcToolBoxEnd(ev.callId, ev.ok, ev.result);
5086
+ break;
5087
+ case 'done':
5088
+ bcFlushStream();
5089
+ bcCloseToolGroup();
5090
+ bcSetBusy(false);
5091
+ bcLine('t-dim', '(tamamlandı)');
5092
+ bcRefreshPreview();
5093
+ if (ideModeOn() && els.bcInput) els.bcInput.focus();
5094
+ break;
5095
+ case 'error':
5096
+ bcFlushStream();
5097
+ bcCloseToolGroup();
5098
+ bcSetBusy(false);
5099
+ bcLine('t-err', '[hata] ' + (ev.error || ''));
5100
+ break;
5101
+ case 'status':
5102
+ /* 'idle' = engine oturumu GERÇEKTEN bıraktı (finally bloğu, her yolda gelir) —
5103
+ done kaçsa bile panel kilitli kalmaz, ■ otomatik kalkar, ring durur */
5104
+ if (ev.status === 'idle') {
5105
+ bcCloseToolGroup();
5106
+ bcSetBusy(false);
5107
+ bcRefreshPreview();
5108
+ }
5109
+ break;
5110
+ }
5111
+ }
5112
+
5113
+ function bcRunCurrent() {
5114
+ const msg = els.bcInput.value.trim();
5115
+ if (!msg) return;
5116
+ els.bcInput.value = '';
5117
+ bcLine('t-cmd', 'code> ' + msg);
5118
+ bcHideTodos(); /* yeni sorgu = eski todo list temizlenir; agent yeniden basacak */
5119
+ bcSetBusy(true);
5120
+ /* sürüyor/serbest kararını main'deki gerçek engine.isBusy verir —
5121
+ panel bayat kilitli kaldıysa ilk mesajla kendini düzeltir */
5122
+ beast.beastcodeSend(msg).then((r) => {
5123
+ if (r && r.ok) {
5124
+ bcSessionId = r.sessionId;
5125
+ } else if (r && r.busy) {
5126
+ /* iş gerçekten sürüyor — kilit açılmaz, bilgi satırı düşer */
5127
+ bcLine('t-dim', (r && r.error) || 'mesaj sürüyor');
5128
+ } else {
5129
+ bcSetBusy(false);
5130
+ bcLine('t-err', (r && r.error) || 'gönderilemedi');
5131
+ }
5132
+ }).catch((e) => {
5133
+ bcSetBusy(false);
5134
+ bcLine('t-err', String((e && e.message) || e));
5135
+ });
5136
+ }
5137
+
5138
+ if (els.bcInput) els.bcInput.addEventListener('keydown', (e) => {
5139
+ if (e.key === 'Enter') { e.preventDefault(); bcRunCurrent(); }
5140
+ });
5141
+ if (els.bcStop) els.bcStop.addEventListener('click', () => beast.beastcodeStop().catch(() => {}));
5142
+ if (els.bcClear) els.bcClear.addEventListener('click', () => {
5143
+ els.bcOut.innerHTML = '';
5144
+ bcTools.clear();
5145
+ bcCloseToolGroup();
5146
+ bcHideTodos();
5147
+ bcFlushStream();
5148
+ });
5149
+ if (els.bcNew) els.bcNew.addEventListener('click', async () => {
5150
+ if (bcRunning) { toast('Mesaj sürüyor — önce ■ ile durdur'); return; }
5151
+ const r = await beast.beastcodeNew().catch(() => null);
5152
+ if (r && r.ok) {
5153
+ bcSessionId = null;
5154
+ bcTools.clear();
5155
+ bcCloseToolGroup();
5156
+ bcHideTodos();
5157
+ bcFlushStream();
5158
+ bcLine('t-sys', 'yeni oturum — sonraki mesaj taze başlar');
5159
+ }
5160
+ });
5161
+ /* Uygulama her açılışta agent (chat) modunda başlar — IDE modu yalnız
5162
+ kullanıcı butona basınca girilir (oturumluk). */