beast-agent 2.11.0 → 2.13.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.
@@ -83,7 +83,34 @@ const els = {
83
83
  bcAttach: $('#bcAttach'),
84
84
  bcMic: $('#bcMic'),
85
85
  bcChips: $('#bcChips'),
86
+ bcQrBtn: $('#bcQrBtn'),
87
+ bcQrCard: $('#bcQrCard'),
88
+ bcQrImg: $('#bcQrImg'),
89
+ bcQrUrl: $('#bcQrUrl'),
86
90
  studioBtn: $('#studioBtn'),
91
+ sbBtn: $('#sbBtn'),
92
+ sandboxRow: $('#sandboxRow'),
93
+ sbRepo: $('#sbRepo'),
94
+ sbReveal: $('#sbReveal'),
95
+ sbRemove: $('#sbRemove'),
96
+ sbNew: $('#sbNew'),
97
+ sbCloneInput: $('#sbCloneInput'),
98
+ sbCloneBtn: $('#sbCloneBtn'),
99
+ sbGhBtn: $('#sbGhBtn'),
100
+ sbOut: $('#sbOut'),
101
+ sbStatus: $('#sbStatus'),
102
+ sbInput: $('#sbInput'),
103
+ sbStop: $('#sbStop'),
104
+ sbRunCwd: $('#sbRunCwd'),
105
+ sbRunInstall: $('#sbRunInstall'),
106
+ sbRunStart: $('#sbRunStart'),
107
+ sbRunDev: $('#sbRunDev'),
108
+ sbRunStop: $('#sbRunStop'),
109
+ sbRunCmd: $('#sbRunCmd'),
110
+ sbRunGo: $('#sbRunGo'),
111
+ sbRunOut: $('#sbRunOut'),
112
+ sbRunOpen: $('#sbRunOpen'),
113
+ sbRunUrl: $('#sbRunUrl'),
87
114
  stPanel: $('#stPanel'),
88
115
  stTitle: $('#stTitle'),
89
116
  stCwd: $('#stCwd'),
@@ -1066,6 +1093,7 @@ function addFiles(files, mode) {
1066
1093
  /* ---------------- settings ---------------- */
1067
1094
 
1068
1095
  let setTab = 'lang'; /* ayarlar artık DİL sekmesiyle açılır */
1096
+ let hrPaneTimer = null; /* headroom-status → Kurulum sekmesi debounce tazeleme */
1069
1097
 
1070
1098
  /* DİL sekmesi: arayüz dili seçimi (eski sağ-alt dil ikonunun yerine) */
1071
1099
  function renderLangPane() {
@@ -1131,6 +1159,7 @@ async function renderActiveSettingsTab() {
1131
1159
  case 'empati': await renderEmpatiPane(); break;
1132
1160
  case 'cron': await openCron(); break;
1133
1161
  case 'usage': await renderUsagePane(); break;
1162
+ case 'headroom': await renderHeadroomPane(); break;
1134
1163
  case 'agents': await refreshAgentsPane(); break;
1135
1164
  case 'logs': await renderLogPane(); break;
1136
1165
  case 'dash': await renderDashboardPane(); break;
@@ -1214,7 +1243,7 @@ function switchTab(name) {
1214
1243
  document.querySelectorAll('#setTabs .tab').forEach((b) =>
1215
1244
  b.classList.toggle('active', b.dataset.tab === name)
1216
1245
  );
1217
- for (const p of ['lang', 'provider', 'fallout', 'skills', 'agents', 'tts', 'install', 'email', 'integrations', 'websearch', 'mcp', 'events', 'empati', 'cron', 'usage', 'logs', 'dash', 'sec', 'update']) {
1246
+ for (const p of ['lang', 'provider', 'fallout', 'skills', 'agents', 'tts', 'install', 'email', 'integrations', 'websearch', 'mcp', 'events', 'empati', 'cron', 'usage', 'headroom', 'logs', 'dash', 'sec', 'update']) {
1218
1247
  const el = $('#tab-' + p);
1219
1248
  if (el) el.hidden = p !== name; // guard: eksik pane tüm sekmeleri kilitlemesin
1220
1249
  }
@@ -1222,6 +1251,7 @@ function switchTab(name) {
1222
1251
  if (name === 'cron') openCron();
1223
1252
  if (name === 'usage') renderUsagePane();
1224
1253
  if (name === 'install') renderInstallPane();
1254
+ if (name === 'headroom') renderHeadroomPane();
1225
1255
  if (name === 'events') renderEventsPane();
1226
1256
  if (name === 'empati') renderEmpatiPane();
1227
1257
  if (name === 'logs') renderLogPane();
@@ -1249,7 +1279,6 @@ async function renderUsagePane() {
1249
1279
  } catch {
1250
1280
  rep = { today: { total: {}, models: [] }, month: { total: {}, models: [] } };
1251
1281
  }
1252
- const wwi = await beast.whereWasIGet().catch(() => ({ enabled: false }));
1253
1282
  const t = rep.today.total || {};
1254
1283
  const mo = rep.month.total || {};
1255
1284
  const card = (label, v) =>
@@ -1284,10 +1313,7 @@ async function renderUsagePane() {
1284
1313
  '<div class="form-grid" style="grid-template-columns:auto auto;gap:8px;margin-top:8px">' +
1285
1314
  '<button id="bkNow" class="btn ghost">' + _t('us_backup_now') + '</button>' +
1286
1315
  '<button id="bkRestore" class="btn ghost">' + _t('us_backup_restore') + '</button>' +
1287
- '</div>' +
1288
- '<div class="divider"></div><h2>' + _t('us_where_h2') + '</h2>' +
1289
- '<div class="sub">' + _t('us_where_sub') + '</div>' +
1290
- `<label class="lock-row" style="margin-top:6px"><input type="checkbox" id="wwiOn" ${wwi.enabled ? 'checked' : ''}/><span>${_t('us_where_on')}</span></label>`;
1316
+ '</div>';
1291
1317
  $('#usReset').addEventListener('click', async () => {
1292
1318
  await beast.resetUsage();
1293
1319
  renderUsagePane();
@@ -1307,15 +1333,6 @@ async function renderUsagePane() {
1307
1333
  toast(_t('us_backup_err') + (r.error || '?'));
1308
1334
  }
1309
1335
  });
1310
- $('#wwiOn').addEventListener('change', async (e) => {
1311
- await beast.whereWasISet({ enabled: e.target.checked });
1312
- toast(e.target.checked ? _t('us_where_on_toast') : _t('us_where_off_toast'));
1313
- });
1314
- /* LİMİT ayarları aynı sekmenin alt bölümünde (Maliyet · Limit birleşik) */
1315
- const limBox = document.createElement('div');
1316
- limBox.id = 'limBox';
1317
- pane.appendChild(limBox);
1318
- await renderLimitsPane();
1319
1336
  }
1320
1337
 
1321
1338
  async function refreshFalloutPane() {
@@ -2192,6 +2209,93 @@ function ttsFlushTail() {
2192
2209
  /* ---------------- KURULUM SEKMESİ: bileşenler OTOMATİK kurulur ----------------
2193
2210
  Eksik/kısmen inmiş bileşen sekme açılınca kendiliğinden indirilir; kullanıcı
2194
2211
  hiçbir düğmeye basmaz. Durum canlı güncellenir (4 sn'de bir). */
2212
+ /* ---------- TOKEN SIKIŞTIRMA (Headroom) sekmesi ----------
2213
+ Opsiyonel yerel sıkıştırma proxy'si: açıkken LLM girdisi %30-70 küçülür.
2214
+ Aç/Kapat buradan; CLI kurulumu Kurulum sekmesinde görülür. */
2215
+ let hrStatsTimer = null;
2216
+
2217
+ async function renderHeadroomPane() {
2218
+ const pane = $('#tab-headroom');
2219
+ if (!pane) return;
2220
+ const st = await beast.headroomStatus().catch(() => null);
2221
+
2222
+ const fmtN = (n) => new Intl.NumberFormat('tr-TR').format(Math.round(Number(n) || 0));
2223
+ let stHtml = '';
2224
+ if (st) {
2225
+ const readyN = (st.proxies || []).filter((p) => p.ready).length;
2226
+ let stateTxt;
2227
+ if (st.installing) stateTxt = '⏳ arka planda kuruluyor — uv tool install headroom-ai[proxy] (birkaç dakika)';
2228
+ else if (st.enabled && readyN) stateTxt = '🟢 açık — proxy: ' + (st.proxies || []).filter((p) => p.ready).map((p) => '127.0.0.1:' + p.port).join(', ');
2229
+ else if (st.enabled) stateTxt = '⏳ proxy başlatılıyor… (ilk açılış ~30 sn)';
2230
+ else if (st.failed) stateTxt = '⛔ ' + (st.error || 'kurulum başarısız');
2231
+ else if (st.installed) stateTxt = '⚪ kapalı — kurulu, aşağıdaki düğmeyle etkinleştir';
2232
+ else stateTxt = '⚪ kapalı — açınca uv ile otomatik kurulur (birkaç dakika)';
2233
+ stHtml = '<div class="sub" style="margin-top:10px;font-size:12px;text-align:center">' + escapeHtml(stateTxt) + '</div>';
2234
+ }
2235
+
2236
+ const stats = await beast.headroomStats().catch(() => null);
2237
+ let statsHtml = '';
2238
+ if (stats && stats.ok && (stats.input || stats.saved)) {
2239
+ statsHtml =
2240
+ '<div class="divider"></div>' +
2241
+ '<h2 style="text-align:center">Tasarruf (proxy açıkken)</h2>' +
2242
+ '<div style="display:flex;gap:24px;flex-wrap:wrap;margin-top:8px;justify-content:center">' +
2243
+ '<div><div style="font-size:20px;font-weight:800;color:var(--ok)">%' + Math.round(stats.savingsPercent || 0) + '</div><div class="sub">sıkıştırma oranı</div></div>' +
2244
+ '<div><div style="font-size:20px;font-weight:800">' + new Intl.NumberFormat('tr-TR').format(stats.proxyCompressionSaved || 0) + '</div><div class="sub">kaydedilen token</div></div>' +
2245
+ '<div><div style="font-size:20px;font-weight:800">' + new Intl.NumberFormat('tr-TR').format(stats.input || 0) + '</div><div class="sub">toplam girdi token</div></div>' +
2246
+ '</div>';
2247
+ }
2248
+
2249
+ /* AÇ/KAPAT düğmesi: ortalanmış + SVG ikonlu (play/pause — uygulama ikon stili) */
2250
+ const svgPlay =
2251
+ '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polygon points="6 3 20 12 6 21 6 3"/></svg>';
2252
+ const svgPause =
2253
+ '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="6" y="4" width="4" height="16" rx="1"/><rect x="14" y="4" width="4" height="16" rx="1"/></svg>';
2254
+ pane.innerHTML =
2255
+ '<h2 style="text-align:center">' + _t('tab_headroom') + '</h2>' +
2256
+ '<div class="sub" style="text-align:center;max-width:620px;margin:0 auto">Headroom yerel proxy\u2019si — araç çıktıları, loglar ve JSON yükleri modele gitmeden sıkıştırılır; orijinali yerelde saklanır. Token faturasını %30-70 düşürür. Yalnız OpenAI-uyumlu sağlayıcı trafiğini etkiler; kapalıyken hiçbir şey değişmez.</div>' +
2257
+ '<div style="display:flex;flex-direction:column;align-items:center;gap:10px;margin-top:18px">' +
2258
+ '<button id="hrToggle" class="btn" style="width:auto;padding:11px 16px;display:inline-flex;align-items:center;gap:8px;justify-content:center" title=""></button>' +
2259
+ '<span id="hrToggleMsg" class="sub" style="margin:0;text-align:center;max-width:560px"></span>' +
2260
+ '</div>' +
2261
+ stHtml + statsHtml;
2262
+
2263
+ const btn = $('#hrToggle');
2264
+ if (btn) {
2265
+ const on = !!(st && st.enabled);
2266
+ /* yalnız SVG ikon — anlamı durum satırı + tooltip taşır */
2267
+ btn.innerHTML = on ? svgPause : svgPlay;
2268
+ btn.title = on ? 'Token sıkıştırmayı kapat' : 'Token sıkıştırmayı aç';
2269
+ btn.setAttribute('aria-label', btn.title);
2270
+ btn.addEventListener('click', async () => {
2271
+ const msg = $('#hrToggleMsg');
2272
+ btn.disabled = true;
2273
+ btn.innerHTML = '<span>…</span>';
2274
+ const r = await beast.headroomToggle(!on).catch(() => null);
2275
+ if (msg) {
2276
+ if (r && r.installing) {
2277
+ msg.textContent = 'Headroom arka planda kuruluyor — birkaç dakika sonra hazır olur, bu sekme açık kalabilir.';
2278
+ } else if (r && r.error) {
2279
+ msg.textContent = r.error;
2280
+ } else {
2281
+ msg.textContent = !on ? 'Açıldı — proxy hazır olunca trafiği yönlendirir' : 'Kapatıldı — trafik sağlayıcıya düz gidiyor';
2282
+ }
2283
+ }
2284
+ setTimeout(() => renderHeadroomPane(), 2500);
2285
+ });
2286
+ }
2287
+
2288
+ /* açıkken istatistikler canlı aksın */
2289
+ if (hrStatsTimer) clearInterval(hrStatsTimer);
2290
+ hrStatsTimer = setInterval(async () => {
2291
+ if (pane.hidden) { clearInterval(hrStatsTimer); return; }
2292
+ const s2 = await beast.headroomStatus().catch(() => null);
2293
+ const cur = $('#hrToggle');
2294
+ if (s2 && cur && cur.disabled) return; /* toggle işlemi sürüyor */
2295
+ await renderHeadroomPane();
2296
+ }, 6000);
2297
+ }
2298
+
2195
2299
  async function renderInstallPane() {
2196
2300
  const pane = $('#tab-install');
2197
2301
  if (!pane) return;
@@ -2259,10 +2363,24 @@ async function renderInstallPane() {
2259
2363
  '<div class="sub" style="font-size:11px">' + escapeHtml(r.detail || '') + (r.mb ? ' · ' + r.mb + ' MB' : '') + '</div>' +
2260
2364
  bar(r) +
2261
2365
  '</div>' +
2366
+ (r.canToggle
2367
+ ? '<button class="btn hr-tgl" data-on="' + (r.toggleOn ? '1' : '0') + '" style="width:auto;padding:2px 12px;flex:none">' + (r.toggleOn ? 'KAPAT' : 'AÇ') + '</button>'
2368
+ : '') +
2262
2369
  badge(r.state) +
2263
2370
  pctLabel(r) +
2264
2371
  '</div>'
2265
2372
  )).join('');
2373
+ /* Headroom aç/kapa: toggle kurulum + proxy başlatmayı arka planda tetikler */
2374
+ el.querySelectorAll('.hr-tgl').forEach((b) => {
2375
+ b.addEventListener('click', async () => {
2376
+ const on = b.dataset.on !== '1';
2377
+ b.disabled = true;
2378
+ b.textContent = '…';
2379
+ const r = await beast.headroomToggle(on).catch(() => null);
2380
+ toast(r && r.error ? r.error : (r && r.installing ? 'Headroom kuruluyor — birkaç dakika sürer' : (on ? 'Headroom açıldı' : 'Headroom kapatıldı')));
2381
+ refresh();
2382
+ });
2383
+ });
2266
2384
  };
2267
2385
 
2268
2386
  const refresh = async () => {
@@ -2829,67 +2947,6 @@ async function renderDashboardPane() {
2829
2947
  pane.appendChild(list);
2830
2948
  }
2831
2949
 
2832
- /* ---------------- Limit: provider bazlı girdi limiti ---------------- */
2833
-
2834
- async function renderLimitsPane() {
2835
- /* Maliyet sekmesine GÖMÜLÜ çalışır (ayrı sekme kaldırıldı) */
2836
- const pane = $('#tab-limits') || $('#limBox');
2837
- if (!pane) return;
2838
- const cur = await beast.getLimits().catch(() => ({ enabled: false, compress: true, default: 0, perProvider: {} }));
2839
- const provs = [...new Set((state.models || []).map((m) => m.providerName))].sort((a, b) => a.localeCompare(b));
2840
-
2841
- const provRows = provs.length
2842
- ? provs
2843
- .map((p) => {
2844
- const v = cur.perProvider[p] || 0;
2845
- return `<div class="usage-row" style="align-items:center">
2846
- <span style="flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${escapeHtml(p)}</span>
2847
- <input class="inp lim-prov" data-prov="${escapeHtml(p)}" type="number" min="0" step="1000"
2848
- value="${v || ''}" placeholder="0 (${_t('lim_unlimited')})" style="width:130px;flex:none;margin:0" />
2849
- </div>`;
2850
- })
2851
- .join('')
2852
- : '<p class="sub">' + _t('p_no_model') + '</p>';
2853
-
2854
- pane.innerHTML =
2855
- '<h2>' + _t('lim_h2') + '</h2>' +
2856
- '<div class="sub">' + _t('lim_sub') + '</div>' +
2857
- `<div class="fo-toggles" style="margin-top:10px">
2858
- <label class="lock-row"><input type="checkbox" id="limOn" ${cur.enabled ? 'checked' : ''}/><span>${_t('lim_enabled')}</span></label>
2859
- </div>
2860
- <div id="limDetail" style="${cur.enabled ? '' : 'opacity:.45'}">
2861
- <label class="lock-row"><input type="checkbox" id="limCompress" ${cur.compress ? 'checked' : ''}/><span>${_t('lim_compress')}</span></label>
2862
- <div class="form-grid" style="grid-template-columns:1fr auto;align-items:center;margin-top:8px">
2863
- <label class="mem-label" style="margin:0">${_t('lim_default')}</label>
2864
- <input id="limDefault" class="inp" type="number" min="0" step="1000" value="${cur.default || ''}" placeholder="0" style="width:130px;flex:none;margin:0" />
2865
- </div>
2866
- <h2 style="margin-top:16px">${_t('lim_prov_h2')}</h2>
2867
- <div class="sub">${_t('lim_prov_sub')}</div>
2868
- <div style="margin-top:6px">${provRows}</div>
2869
- <button id="limSave" class="btn ghost" style="margin-top:12px">${_t('lim_save')}</button>
2870
- </div>`;
2871
-
2872
- const det = pane.querySelector('#limDetail');
2873
- pane.querySelector('#limOn').addEventListener('change', (e) => {
2874
- det.style.opacity = e.target.checked ? '' : '.45';
2875
- });
2876
-
2877
- pane.querySelector('#limSave').addEventListener('click', async () => {
2878
- const perProvider = {};
2879
- for (const inp of pane.querySelectorAll('.lim-prov')) {
2880
- const n = Math.round(Number(inp.value) || 0);
2881
- if (n > 0) perProvider[inp.dataset.prov] = n;
2882
- }
2883
- await beast.setLimits({
2884
- enabled: pane.querySelector('#limOn').checked,
2885
- compress: pane.querySelector('#limCompress').checked,
2886
- default: Math.round(Number(pane.querySelector('#limDefault').value) || 0),
2887
- perProvider,
2888
- });
2889
- toast(_t('lim_saved_toast'));
2890
- });
2891
- }
2892
-
2893
2950
  /* ---------------- Güvenlik: ajan onay davranışı ---------------- */
2894
2951
 
2895
2952
  async function renderSecurityPane() {
@@ -4959,6 +5016,19 @@ function onEvent(ev) {
4959
5016
  return;
4960
5017
  }
4961
5018
  if (ev.type === 'install-progress') { updateInstallPct(ev); return; }
5019
+ /* Headroom durum değişimi → açık olan sekmeyi tazele (Token Sıkıştırma / Kurulum) */
5020
+ if (ev.type === 'headroom-status') {
5021
+ try {
5022
+ const iPane = document.getElementById('tab-install');
5023
+ const hPane = document.getElementById('tab-headroom');
5024
+ const open = (!iPane || iPane.hidden) && (!hPane || hPane.hidden) ? '' : (!hPane || hPane.hidden ? 'install' : 'headroom');
5025
+ if (open) {
5026
+ clearTimeout(hrPaneTimer);
5027
+ hrPaneTimer = setTimeout(() => { (open === 'headroom' ? renderHeadroomPane() : renderInstallPane()).catch(() => {}); }, 1500);
5028
+ }
5029
+ } catch {}
5030
+ return;
5031
+ }
4962
5032
  /* EMPATİ LOOP: proaktif bildirim (masaüstü) + sekme canlı yenileme.
4963
5033
  Toast'ta yalnız haber başlığı — link/markdown kalabalığı chat mesajında. */
4964
5034
  if (ev.type === 'proactive') {
@@ -4984,6 +5054,19 @@ function onEvent(ev) {
4984
5054
  }
4985
5055
  /* OFFLINE MESAJ KUYRUĞU: bağlantı + kuyruk olayları (sessionId filtresinden önce) */
4986
5056
  if (ev.type === 'net' || ev.type === 'netQueue') { onNetEvent(ev); return; }
5057
+ /* Sandbox klonlama progress'i (sessionId'siz global olay) */
5058
+ if (ev.type === 'sb-clone') { sbCloneProgress(ev.pct, ev.text, !!ev.done); return; }
5059
+ /* Sandbox ÇALIŞTIRICI süreç akışı: sessionId'siz global olaylar */
5060
+ if (ev.type === 'sb-run' || ev.type === 'sb-run-url' || ev.type === 'sb-run-end') {
5061
+ sbRunIngest(ev);
5062
+ return;
5063
+ }
5064
+ /* Beast Sandbox oturumu: olaylar SADECE Sandbox paneline akar — ana sohbeti
5065
+ ve Code/Studio panellerini kirletmez (dünyalar ayrı) */
5066
+ if (sbSessionId && ev.sessionId === sbSessionId) {
5067
+ sbIngest(ev);
5068
+ return;
5069
+ }
4987
5070
  /* Beast Studio oturumu: olaylar SADECE Studio paneline akar — ana sohbeti
4988
5071
  ve Beast Code panelini kirletmez (dünyalar ayrı) */
4989
5072
  if (stSessionId && ev.sessionId === stSessionId) {
@@ -6896,6 +6979,7 @@ function ghCard(repo, isFav) {
6896
6979
  <div class="gh-actions">
6897
6980
  <button class="gh-fav ${isFav ? 'on' : ''}" data-act="fav">${isFav ? '★ Favori' : '☆ Favorile'}</button>
6898
6981
  <button data-act="chat"><svg viewBox="0 0 24 24" width="11" height="11" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-1px;margin-right:4px"><path d="M22 2 11 13"/><path d="M22 2 15 22l-4-9-9-4z"/></svg>Chate at</button>
6982
+ <button data-act="dl" title="Sandbox'a indir (git clone)">⬇ İndir</button>
6899
6983
  <button data-act="copy">⧉ Kopyala</button>
6900
6984
  </div>
6901
6985
  </div>`;
@@ -6911,6 +6995,12 @@ function wireGhCards(container, list, isFavPane) {
6911
6995
  /* mesaj chate gitti — cevap chatten geleceği için modal kapanır */
6912
6996
  closeGit();
6913
6997
  });
6998
+ /* Sandbox'a indir: klonlama Sandbox panelinde görünür, panel açılır */
6999
+ card.querySelector('[data-act="dl"]').addEventListener('click', async () => {
7000
+ closeGit();
7001
+ setSandboxMode(true);
7002
+ sbClone(repo.full_name);
7003
+ });
6914
7004
  card.querySelector('[data-act="copy"]').addEventListener('click', async () => {
6915
7005
  try { await navigator.clipboard.writeText(repo.html_url); toast('Link kopyalandı'); } catch {}
6916
7006
  });
@@ -7068,6 +7158,7 @@ function ideModeOn() {
7068
7158
 
7069
7159
  async function setIdeMode(on) {
7070
7160
  if (on && studioModeOn()) await setStudioMode(false); /* Studio açıkken IDE'ye geçiş — Studio kapanır */
7161
+ if (on && sbModeOn()) setSandboxMode(false); /* Sandbox açıkken IDE'ye geçiş — Sandbox kapanır */
7071
7162
  document.body.classList.toggle('ide-mode', !!on);
7072
7163
  if (els.ideBtn) els.ideBtn.classList.toggle('on', !!on);
7073
7164
  /* soldaki marka: chat modunda BEAST Agent, IDE modunda BEAST Code, Studio modunda BEAST Studio */
@@ -7095,6 +7186,10 @@ async function setIdeMode(on) {
7095
7186
  bcBanner();
7096
7187
  renderBcHistory(); /* soldaki geçmiş listesi: IDE'ye girişte tazelenir */
7097
7188
  codeGutterRender(); /* dosya açık olmasa bile rakamlar görünür */
7189
+ /* Expo QR daha önce üretildiyse kart geri gelsin */
7190
+ beast.ideExpQr().then((r) => {
7191
+ if (r && r.ok && r.qr) bcQrShow(r.qr, r.url);
7192
+ }).catch(() => {});
7098
7193
  } else {
7099
7194
  /* terminal bölmeye alınmadan önce kapatılır, sonra ALT DOCK'a geri taşınır */
7100
7195
  if (termOpen) termSetOpen(false);
@@ -7137,6 +7232,7 @@ function studioModeOn() {
7137
7232
 
7138
7233
  async function setStudioMode(on) {
7139
7234
  if (on && ideModeOn()) await setIdeMode(false); /* IDE açıkken Studio'ya geçiş — IDE kapanır */
7235
+ if (on && sbModeOn()) setSandboxMode(false); /* Sandbox açıkken Studio'ya geçiş — Sandbox kapanır */
7140
7236
  document.body.classList.toggle('studio-mode', !!on);
7141
7237
  if (els.studioBtn) els.studioBtn.classList.toggle('on', !!on);
7142
7238
  /* ALT TERMINAL: yalnız Beast Code'a özgü — Studio'ya geçişte kapanır */
@@ -7359,7 +7455,8 @@ if (els.ideSplit) {
7359
7455
  }
7360
7456
 
7361
7457
  async function loadIdeTree() {
7362
- ideState.cache.clear();
7458
+ /* hangi moddaysak o ağacın önbelleği tazelenir (Sandbox → repo ağacı) */
7459
+ (sbModeOn() ? sbTreeState : studioModeOn() ? studioState : ideState).cache.clear();
7363
7460
  await renderFileTree();
7364
7461
  }
7365
7462
 
@@ -7375,17 +7472,26 @@ async function renderFileTree() {
7375
7472
  if (!tree) return;
7376
7473
  tree.innerHTML = '';
7377
7474
  const wsLabel = $('#filePanelPath');
7378
- /* Studio modunda ağaç studioRoot'tan, IDE modunda ideRoot'tan okunur —
7379
- aynı panel, aynı çizim; yalnız kaynak ve davranış moduna göre değişir */
7475
+ /* Studio modunda ağaç studioRoot'tan, Sandbox modunda seçili reponun
7476
+ klasöründen, IDE modunda ideRoot'tan okunur — aynı panel, aynı çizim */
7477
+ const sbx = sbModeOn();
7380
7478
  const studio = studioModeOn();
7381
- const st = studio ? studioState : ideState;
7382
- const treeApi = studio ? beast.studioTree : beast.ideTree;
7479
+ const st = sbx ? sbTreeState : studio ? studioState : ideState;
7480
+ const treeApi = sbx
7481
+ ? (rel) => beast.sandboxTree({ folder: sbFolder, rel })
7482
+ : studio
7483
+ ? beast.studioTree
7484
+ : beast.ideTree;
7383
7485
  const build = async (rel, depth, container) => {
7384
7486
  let entries = st.cache.get(rel);
7385
7487
  if (!entries) {
7386
7488
  const r = await treeApi(rel).catch(() => null);
7387
7489
  if (!r || !r.ok) {
7388
- if (!rel) container.innerHTML = `<div class="file-empty">${escapeHtml((r && r.error) || 'okunamadı')}</div>`;
7490
+ if (!rel) {
7491
+ container.innerHTML = sbx
7492
+ ? '<div class="file-empty">Repo seçili değil — yukarıdan indir ya da seç</div>'
7493
+ : `<div class="file-empty">${escapeHtml((r && r.error) || 'okunamadı')}</div>`;
7494
+ }
7389
7495
  return;
7390
7496
  }
7391
7497
  entries = r.entries;
@@ -7393,7 +7499,9 @@ async function renderFileTree() {
7393
7499
  if (r.workspace) {
7394
7500
  st.path = r.workspace;
7395
7501
  if (wsLabel) { wsLabel.textContent = r.workspace; wsLabel.title = r.workspace; }
7396
- if (studio) {
7502
+ if (sbx) {
7503
+ if (els.sbRunCwd) { els.sbRunCwd.textContent = r.workspace; els.sbRunCwd.title = r.workspace; }
7504
+ } else if (studio) {
7397
7505
  if (els.stCwd) { els.stCwd.textContent = r.workspace; els.stCwd.title = r.workspace; }
7398
7506
  stOnFolderChanged(r.workspace);
7399
7507
  } else {
@@ -7418,6 +7526,13 @@ async function renderFileTree() {
7418
7526
  if (st.open.has(child)) st.open.delete(child);
7419
7527
  else st.open.add(child);
7420
7528
  renderFileTree();
7529
+ } else if (sbx) {
7530
+ /* Sandbox: dosyayı chat'e bağlam olarak bırak — ajan incelesin */
7531
+ if (els.sbInput) {
7532
+ els.sbInput.value = 'Şu dosyayı incele: ' + child;
7533
+ els.sbInput.focus();
7534
+ sbInputResize();
7535
+ }
7421
7536
  } else if (studio && MEDIA_FILE_RE.test(e.name)) {
7422
7537
  stVideoPlay(child); /* video/ses → sağdaki video bölümünde oynat */
7423
7538
  } else if (studio) {
@@ -7449,7 +7564,8 @@ function ideRefreshTree() {
7449
7564
  ideTreeTimer = null;
7450
7565
  try {
7451
7566
  /* hangi moddaysak o ağacın önbelleği tazelenir */
7452
- if (studioModeOn()) studioState.cache.clear();
7567
+ if (sbModeOn()) sbTreeState.cache.clear();
7568
+ else if (studioModeOn()) studioState.cache.clear();
7453
7569
  else ideState.cache.clear();
7454
7570
  renderFileTree().catch(() => {});
7455
7571
  } catch {}
@@ -7943,11 +8059,32 @@ async function undoTask(id) {
7943
8059
  if (els.fileTasks) els.fileTasks.addEventListener('click', () => toggleTasksPanel());
7944
8060
 
7945
8061
  $('#filePreview').addEventListener('click', async () => {
7946
- const r = await beast.idePreview().catch(() => null);
7947
- if (r && r.ok) {
7948
- if (ideModeOn() === false) setIdeMode(true);
7949
- } else {
8062
+ if (!ideModeOn()) await setIdeMode(true);
8063
+ /* proje tipi algıla: statik site ajan gerektirmez, dev-server projelerinde
8064
+ HER PROJE FARKLI KOMUTLA çalışır — komutu ajan package.json'a bakıp seçer */
8065
+ const info = await beast.ideProjInfo().catch(() => null);
8066
+ const kind = (info && info.kind) || 'unknown';
8067
+ if (kind === 'static') {
8068
+ const r = await beast.idePreview().catch(() => null);
8069
+ if (r && r.ok) return;
7950
8070
  toast((r && r.error) || 'preview açılamadı');
8071
+ return;
8072
+ }
8073
+ const ask =
8074
+ kind === 'expo'
8075
+ ? 'Preview aç — bu klasör bir Expo/React Native projesi (app.json var). Web önizlemeyi arka planda başlat: package.json scripts.web varsa `npm run web`, yoksa `npx expo start --web`. Sunucu kalkınca çalışan http://localhost:PORT adresini raporda yaz.'
8076
+ : kind === 'react-native'
8077
+ ? 'Preview aç — bu klasör bir React Native projesi. Metro sunucusunu arka planda başlat: package.json scripts.start varsa `npm start`, yoksa `npx react-native start`. Sunucu kalkınca çalışan http://localhost:PORT adresini raporda yaz.'
8078
+ : kind === 'web'
8079
+ ? 'Preview aç — bu proje dev-server ister. Doğru komutu package.json scripts\'ından seç (dev/start/serve — `npm run dev` vb.) ve arka planda başlat. Sunucu kalkınca çalışan http://localhost:PORT adresini raporda yaz.'
8080
+ : 'Preview aç — bu projenin nasıl çalıştığını package.json / konfigürasyon dosyalarından anla, doğru dev komutunu arka planda başlat ve çalışan http://localhost:PORT adresini raporda yaz.';
8081
+ if (els.bcInput) {
8082
+ els.bcInput.value = ask;
8083
+ bcInputResize();
8084
+ bcRunCurrent();
8085
+ toast('Ajan preview görevini aldı');
8086
+ } else {
8087
+ toast('Beast Code paneli yok');
7951
8088
  }
7952
8089
  });
7953
8090
  if (els.codeSave) els.codeSave.addEventListener('click', codeSave);
@@ -8028,8 +8165,9 @@ function fileCtxShow(e, rel, isDir) {
8028
8165
  const menu = els.fileCtxMenu;
8029
8166
  if (!menu || !rel) return;
8030
8167
  e.preventDefault();
8168
+ const sbx = sbModeOn();
8031
8169
  const studio = studioModeOn();
8032
- const st = studio ? studioState : ideState;
8170
+ const st = sbx ? sbTreeState : studio ? studioState : ideState;
8033
8171
  const isMedia = MEDIA_FILE_RE.test(rel.split('/').pop() || '');
8034
8172
  const items = [];
8035
8173
  if (isDir) {
@@ -8041,6 +8179,22 @@ function fileCtxShow(e, rel, isDir) {
8041
8179
  renderFileTree();
8042
8180
  },
8043
8181
  });
8182
+ } else if (sbx) {
8183
+ /* Sandbox: dosya düzenleme Code moduna aittir — chate bağlam bırak/aç */
8184
+ items.push({
8185
+ label: 'Chate ekle (şu dosyaya bak)',
8186
+ fn: () => {
8187
+ if (els.sbInput) {
8188
+ els.sbInput.value = 'Şu dosyayı incele: ' + rel;
8189
+ els.sbInput.focus();
8190
+ sbInputResize();
8191
+ }
8192
+ },
8193
+ });
8194
+ items.push({
8195
+ label: 'Dış uygulamada aç',
8196
+ fn: () => beast.openPath((sbTreeState.path || '') + '\\' + rel.replace(/\//g, '\\')).catch(() => {}),
8197
+ });
8044
8198
  } else if (studio && isMedia) {
8045
8199
  items.push({ label: 'Oynat', fn: () => stVideoPlay(rel) });
8046
8200
  } else if (studio) {
@@ -8695,6 +8849,10 @@ function bcIngest(ev) {
8695
8849
  /* iş bitince üretilen site/app dahili tarayıcıda CANLI açılır */
8696
8850
  bcAutoPreview(ev.url);
8697
8851
  break;
8852
+ case 'bc-exp-qr':
8853
+ /* Expo dev server exp:// adresi → telefonda Expo Go için QR kart */
8854
+ bcQrShow(ev.qr, ev.url);
8855
+ break;
8698
8856
  case 'bc-mode':
8699
8857
  /* OpenCode disiplini çalışma modu: başlıkta rozet + panelde bilgi satırı */
8700
8858
  if (els.bcTitle) {
@@ -8822,6 +8980,33 @@ if (els.bcClear) els.bcClear.addEventListener('click', () => {
8822
8980
  bcHideTodos();
8823
8981
  bcFlushStream();
8824
8982
  });
8983
+
8984
+ /* ---------- Expo Go QR kartı (yalnız Beast Code) ---------- */
8985
+ function bcQrShow(qr, url) {
8986
+ if (!els.bcQrCard || !ideModeOn() || !qr) return;
8987
+ if (els.bcQrImg) els.bcQrImg.src = qr;
8988
+ if (els.bcQrUrl) {
8989
+ els.bcQrUrl.textContent = url || '';
8990
+ els.bcQrUrl.title = url || '';
8991
+ }
8992
+ if (els.bcQrBtn) els.bcQrBtn.hidden = false;
8993
+ els.bcQrCard.hidden = false;
8994
+ }
8995
+ if (els.bcQrBtn) els.bcQrBtn.addEventListener('click', () => {
8996
+ if (!els.bcQrCard) return;
8997
+ if (els.bcQrCard.hidden) {
8998
+ beast.ideExpQr().then((r) => {
8999
+ if (r && r.ok && r.qr) bcQrShow(r.qr, r.url);
9000
+ else toast('QR henüz yok — ajan expo start çalıştırınca çıkar');
9001
+ }).catch(() => {});
9002
+ } else {
9003
+ els.bcQrCard.hidden = true;
9004
+ }
9005
+ });
9006
+ if (els.bcQrCard) {
9007
+ const qclose = els.bcQrCard.querySelector('#bcQrClose');
9008
+ if (qclose) qclose.addEventListener('click', () => { els.bcQrCard.hidden = true; });
9009
+ }
8825
9010
  if (els.bcNew) els.bcNew.addEventListener('click', async () => {
8826
9011
  if (bcRunning) { toast('Mesaj sürüyor — önce ■ ile durdur'); return; }
8827
9012
  const r = await beast.beastcodeNew().catch(() => null);
@@ -9247,3 +9432,463 @@ if (els.stNew) els.stNew.addEventListener('click', async () => {
9247
9432
  });
9248
9433
  /* Uygulama her açılışta agent (chat) modunda başlar — IDE modu yalnız
9249
9434
  kullanıcı butona basınca girilir (oturumluk). */
9435
+
9436
+ /* ================= BEAST SANDBOX =================
9437
+ GitHub reposu indir (git clone) + repo klasöründe ajanla çalış.
9438
+ Sağ dock — ana chat solda yerinde kalır; Code/Studio'dan tamamen ayrı. */
9439
+
9440
+ let sbSessionId = '';
9441
+ let sbFolder = '';
9442
+ let sbBusy = false;
9443
+ let sbItems = [];
9444
+ let sbStreamEl = null;
9445
+ let sbStreamRaw = '';
9446
+ let sbCloning = false;
9447
+ /* sol dosya konsolu (repo ağacı) + sağ çalıştırıcı durumu */
9448
+ const sbTreeState = { cache: new Map(), open: new Set(['']), path: '' };
9449
+ let sbRunUrl = '';
9450
+ let sbRunRunning = false;
9451
+
9452
+ function sbModeOn() {
9453
+ return document.body.classList.contains('sandbox-mode');
9454
+ }
9455
+
9456
+ function sbLine(cls, text) {
9457
+ if (!els.sbOut) return null;
9458
+ const div = document.createElement('div');
9459
+ div.className = cls || 't-out';
9460
+ div.textContent = String(text || '');
9461
+ els.sbOut.appendChild(div);
9462
+ els.sbOut.scrollTop = els.sbOut.scrollHeight;
9463
+ return div;
9464
+ }
9465
+
9466
+ function sbStreamDelta(delta) {
9467
+ if (!els.sbOut) return;
9468
+ if (!sbStreamEl) {
9469
+ sbStreamEl = sbLine('t-out', '');
9470
+ }
9471
+ sbStreamRaw += String(delta || '');
9472
+ sbStreamEl.textContent = sbStreamRaw;
9473
+ els.sbOut.scrollTop = els.sbOut.scrollHeight;
9474
+ }
9475
+
9476
+ function sbFlushStream() {
9477
+ sbStreamEl = null;
9478
+ sbStreamRaw = '';
9479
+ }
9480
+
9481
+ function sbMsgText(content) {
9482
+ const t = Array.isArray(content)
9483
+ ? content.filter((p) => p && p.type === 'text').map((p) => String(p.text || '')).join('\n')
9484
+ : String(content || '');
9485
+ return t.replace(/\s+/g, ' ').trim();
9486
+ }
9487
+
9488
+ function sbArgsSummary(args) {
9489
+ try {
9490
+ const j = typeof args === 'string' ? JSON.parse(args) : args || {};
9491
+ return String(j.command || j.path || j.url || j.query || j.pattern || '').replace(/\s+/g, ' ').slice(0, 90);
9492
+ } catch {
9493
+ return '';
9494
+ }
9495
+ }
9496
+
9497
+ function sbSetBusy(b) {
9498
+ sbBusy = !!b;
9499
+ if (els.sbStop) els.sbStop.hidden = !sbBusy;
9500
+ }
9501
+
9502
+ function sbStatusShow(text) {
9503
+ if (els.sbStatus) {
9504
+ els.sbStatus.hidden = false;
9505
+ els.sbStatus.textContent = text;
9506
+ }
9507
+ }
9508
+ function sbStatusHide() {
9509
+ if (els.sbStatus) els.sbStatus.hidden = true;
9510
+ }
9511
+
9512
+ /* Sandbox oturumunun engine olayları → panel (bcIngest'in minimal karşılığı) */
9513
+ function sbIngest(ev) {
9514
+ if (!sbSessionId || ev.sessionId !== sbSessionId) return;
9515
+ switch (ev.type) {
9516
+ case 'token':
9517
+ sbStreamDelta(ev.delta);
9518
+ break;
9519
+ case 'message': {
9520
+ const m = ev.message;
9521
+ if (!m) break;
9522
+ /* KULLANICI ECHO BASILMAZ — gönderirken zaten 'sen>' satırı düştü
9523
+ (Beast Code paneliyle aynı disiplin; yoksa mesaj ÇİFT görünür) */
9524
+ if (m.role === 'assistant') {
9525
+ sbFlushStream();
9526
+ const hasTools = Array.isArray(m.tool_calls) && m.tool_calls.length > 0;
9527
+ const txt = typeof m.content === 'string' ? m.content.trim() : '';
9528
+ if (txt && !hasTools) sbLine('t-out', txt);
9529
+ }
9530
+ break;
9531
+ }
9532
+ case 'tool-start':
9533
+ sbSetBusy(true);
9534
+ sbLine('t-dim', '\u2692 ' + (ev.name || '?') + (sbArgsSummary(ev.args) ? ' \u00B7 ' + sbArgsSummary(ev.args) : ''));
9535
+ break;
9536
+ case 'tool-end': {
9537
+ const ok = ev.ok !== false;
9538
+ sbLine(ok ? 't-dim' : 't-err', (ok ? ' \u2713 ' : ' \u2717 ') + String(ev.result || '').replace(/\s+/g, ' ').slice(0, 160));
9539
+ /* ajan dosya yazarsa/editlerse soldaki repo ağacı canlı tazelenir */
9540
+ if (ev.name === 'write_file' || ev.name === 'edit_file' || ev.name === 'run_command') ideRefreshTree();
9541
+ break;
9542
+ }
9543
+ case 'status':
9544
+ if (ev.status === 'idle') {
9545
+ sbSetBusy(false);
9546
+ sbStatusHide();
9547
+ } else {
9548
+ sbSetBusy(true);
9549
+ sbStatusShow(ev.status === 'thinking' ? 'düşünüyor…' : String(ev.status || ''));
9550
+ }
9551
+ break;
9552
+ case 'done':
9553
+ sbFlushStream();
9554
+ sbSetBusy(false);
9555
+ sbStatusHide();
9556
+ sbLine('t-dim', ev.aborted ? '[durduruldu' + (ev.reason ? ' — ' + ev.reason : '') + ']' : '(tamamlandı)');
9557
+ break;
9558
+ case 'error':
9559
+ sbFlushStream();
9560
+ sbSetBusy(false);
9561
+ sbStatusHide();
9562
+ sbLine('t-err', '[hata] ' + (ev.error || ''));
9563
+ break;
9564
+ }
9565
+ }
9566
+
9567
+ async function loadSandboxList() {
9568
+ const r = await beast.sandboxList().catch(() => ({ ok: false }));
9569
+ sbItems = (r && r.items) || [];
9570
+ const sel = els.sbRepo;
9571
+ if (!sel) return;
9572
+ sel.innerHTML = '';
9573
+ if (!sbItems.length) {
9574
+ const o = document.createElement('option');
9575
+ o.value = '';
9576
+ o.textContent = 'Repo yok, indir';
9577
+ sel.appendChild(o);
9578
+ }
9579
+ for (const it of sbItems) {
9580
+ const o = document.createElement('option');
9581
+ o.value = it.folder;
9582
+ o.textContent = it.name + (it.busy ? ' ●' : '');
9583
+ sel.appendChild(o);
9584
+ }
9585
+ if (sbFolder && sbItems.some((x) => x.folder === sbFolder)) sel.value = sbFolder;
9586
+ else if (sbItems.length) {
9587
+ sel.value = sbItems[0].folder;
9588
+ sbFolder = sel.value;
9589
+ } else {
9590
+ sbFolder = '';
9591
+ }
9592
+ await sbSelectCurrent();
9593
+ }
9594
+
9595
+ async function sbSelectCurrent() {
9596
+ sbFolder = els.sbRepo ? String(els.sbRepo.value || '') : '';
9597
+ if (els.sbOut) els.sbOut.innerHTML = '';
9598
+ sbSessionId = '';
9599
+ sbStreamEl = null;
9600
+ sbStreamRaw = '';
9601
+ sbSetBusy(false);
9602
+ sbStatusHide();
9603
+ /* çalıştırıcı + ağaç yeni repoya döner */
9604
+ sbRunUrl = '';
9605
+ sbRunRunning = false;
9606
+ if (els.sbRunOpen) els.sbRunOpen.disabled = true;
9607
+ if (els.sbRunUrl) els.sbRunUrl.textContent = '';
9608
+ if (els.sbRunOut) els.sbRunOut.innerHTML = '';
9609
+ sbTreeState.cache.clear();
9610
+ sbTreeState.open = new Set(['']);
9611
+ if (sbModeOn()) renderFileTree().catch(() => {});
9612
+ if (!sbFolder) {
9613
+ sbLine('t-dim', 'Repo indir: kutuya github.com/owner/repo yaz → İndir. Ya da 🔍 ile GitHub\u2019da ara.');
9614
+ return;
9615
+ }
9616
+ const r = await beast.sandboxOpen({ folder: sbFolder }).catch(() => null);
9617
+ if (r && r.ok) {
9618
+ sbSessionId = r.sessionId;
9619
+ for (const m of r.messages || []) {
9620
+ sbLine(m.role === 'assistant' ? 't-out' : 't-cmd', (m.role === 'assistant' ? '' : 'sen> ') + m.text);
9621
+ }
9622
+ sbLine('t-dim', 'Oturum yüklendi');
9623
+ } else {
9624
+ sbLine('t-err', (r && r.error) || 'oturum açılamadı');
9625
+ }
9626
+ sbSetBusy(!!(r && r.busy));
9627
+ /* sol konsol: seçilen reponun klasörü — oturum açıldıktan sonra da garanti tazele */
9628
+ sbTreeState.cache.clear();
9629
+ if (sbModeOn()) renderFileTree().catch(() => {});
9630
+ }
9631
+
9632
+ function sbInputResize() {
9633
+ const ta = els.sbInput;
9634
+ if (!ta) return;
9635
+ ta.style.height = 'auto';
9636
+ ta.style.height = Math.min(ta.scrollHeight, 140) + 'px';
9637
+ ta.classList.toggle('expand', ta.scrollHeight > 140);
9638
+ }
9639
+
9640
+ function sbRunCurrent() {
9641
+ const msg = els.sbInput.value.trim();
9642
+ if (!msg || !sbFolder || sbCloning) return;
9643
+ els.sbInput.value = '';
9644
+ sbInputResize();
9645
+ sbLine('t-cmd', 'sen> ' + msg);
9646
+ sbSetBusy(true);
9647
+ beast.sandboxSend({ folder: sbFolder, msg }).then((r) => {
9648
+ if (r && r.ok) {
9649
+ if (r.sessionId) sbSessionId = r.sessionId;
9650
+ if (r.queued) sbLine('t-dim', '⏳ kuyrukta (' + r.count + ') — iş bitince gönderilir');
9651
+ } else {
9652
+ sbSetBusy(false);
9653
+ sbLine('t-err', (r && r.error) || 'gönderilemedi');
9654
+ }
9655
+ }).catch((e) => {
9656
+ sbSetBusy(false);
9657
+ sbLine('t-err', String((e && e.message) || e));
9658
+ });
9659
+ }
9660
+
9661
+ let sbCloneLine = null;
9662
+ function sbCloneProgress(pct, text, done) {
9663
+ /* buton etiketi: %34 · İndir */
9664
+ const label = document.getElementById('sbCloneLabel');
9665
+ if (label) label.textContent = pct != null ? '%' + pct : 'İndir';
9666
+ if (!els.sbOut) return;
9667
+ if (done) {
9668
+ if (sbCloneLine && sbCloneLine.isConnected) sbCloneLine.remove();
9669
+ sbCloneLine = null;
9670
+ return;
9671
+ }
9672
+ if (!sbCloneLine || !sbCloneLine.isConnected) {
9673
+ sbCloneLine = sbLine('t-dim', '');
9674
+ }
9675
+ if (sbCloneLine) {
9676
+ sbCloneLine.textContent = (pct != null ? '⬇ %' + pct : '⬇ klonlanıyor') + (text ? ' · ' + text : '');
9677
+ els.sbOut.scrollTop = els.sbOut.scrollHeight;
9678
+ }
9679
+ }
9680
+
9681
+ async function sbClone(input) {
9682
+ if (sbCloning) return;
9683
+ const src = String(input || '').trim();
9684
+ if (!src) {
9685
+ toast('Repo adresi gir: github.com/owner/repo');
9686
+ return;
9687
+ }
9688
+ sbCloning = true;
9689
+ if (els.sbCloneBtn) els.sbCloneBtn.disabled = true;
9690
+ sbCloneLine = null;
9691
+ sbLine('t-dim', '⬇ klonlanıyor: ' + src + ' — (büyük repolar sürebilir…)');
9692
+ const r = await beast.sandboxClone(src).catch((e) => ({ ok: false, error: String((e && e.message) || e) }));
9693
+ sbCloning = false;
9694
+ if (els.sbCloneBtn) els.sbCloneBtn.disabled = false;
9695
+ sbCloneProgress(null, '', true); /* progress satırını temizle + etiketi geri koy */
9696
+ if (r && r.ok) {
9697
+ sbLine('t-dim', '✓ indirildi: ' + r.name);
9698
+ if (els.sbCloneInput) els.sbCloneInput.value = '';
9699
+ sbFolder = r.folder;
9700
+ await loadSandboxList();
9701
+ if (els.sbInput) els.sbInput.focus();
9702
+ } else {
9703
+ sbLine('t-err', '[klonlama] ' + ((r && r.error) || 'başarısız'));
9704
+ }
9705
+ }
9706
+
9707
+ function setSandboxMode(on) {
9708
+ if (on && ideModeOn()) { setIdeMode(false); }
9709
+ if (on && studioModeOn()) { setStudioMode(false); }
9710
+ document.body.classList.toggle('sandbox-mode', !!on);
9711
+ if (els.sbBtn) els.sbBtn.classList.toggle('on', !!on);
9712
+ /* marka + sol konsol başlığı: moduna göre BEAST Agent/Code/Studio/Sandbox */
9713
+ const brandSub = document.querySelector('#brand .brand-sub');
9714
+ if (brandSub) brandSub.textContent = on ? 'Sandbox' : 'Agent';
9715
+ const ftp = document.getElementById('filePanelTitle');
9716
+ if (ftp && on) ftp.textContent = 'REPO DOSYALARI';
9717
+ if (on) {
9718
+ loadSandboxList();
9719
+ sbTreeState.cache.clear();
9720
+ renderFileTree().catch(() => {});
9721
+ } else if (!ideModeOn() && !studioModeOn()) {
9722
+ /* agent moduna dönüş: ağaç eski köküne döner */
9723
+ ideState.cache.clear();
9724
+ renderFileTree().catch(() => {});
9725
+ }
9726
+ }
9727
+
9728
+ /* ---------- ÇALIŞTIRICI: butonlar KOMUT DEĞİL AJANA TALİMATTIR ----------
9729
+ Her proje farklı çalışır; komutu kullanıcı bilemez, AJAN bilir:
9730
+ paket bildirim dosyasına + README'ye bakıp doğru komutu bulur, uzun süreli
9731
+ süreci panel_run aracıyla ÇALIŞTIR panelinde (turu kilitlemeden) başlatır. */
9732
+ function sbInstruct(text) {
9733
+ if (!sbFolder) { toast('Önce repo indir ya da seç'); return; }
9734
+ sbLine('t-cmd', 'sen> ' + text);
9735
+ sbSetBusy(true);
9736
+ beast.sandboxSend({ folder: sbFolder, msg: text }).then((r) => {
9737
+ if (r && r.ok) {
9738
+ if (r.sessionId) sbSessionId = r.sessionId;
9739
+ if (r.queued) sbLine('t-dim', '⏳ kuyrukta (' + r.count + ') — iş bitince gönderilir');
9740
+ } else {
9741
+ sbSetBusy(false);
9742
+ sbLine('t-err', (r && r.error) || 'gönderilemedi');
9743
+ }
9744
+ }).catch((e) => {
9745
+ sbSetBusy(false);
9746
+ sbLine('t-err', String((e && e.message) || e));
9747
+ });
9748
+ }
9749
+
9750
+ function sbRunLine(text, cls) {
9751
+ if (!els.sbRunOut) return null;
9752
+ const div = document.createElement('div');
9753
+ div.className = cls || 't-out';
9754
+ div.textContent = String(text || '');
9755
+ els.sbRunOut.appendChild(div);
9756
+ els.sbRunOut.scrollTop = els.sbRunOut.scrollHeight;
9757
+ return div;
9758
+ }
9759
+
9760
+ /* süreç çıktısı akışı: sessionId'siz global olaylar — en üstte yönlendirilir */
9761
+ function sbRunIngest(ev) {
9762
+ if (ev.type === 'sb-run') {
9763
+ sbRunLine(ev.data);
9764
+ return;
9765
+ }
9766
+ if (ev.type === 'sb-run-url') {
9767
+ sbRunUrl = String(ev.url || '');
9768
+ sbRunLine('🌐 dev server: ' + sbRunUrl, 't-dim');
9769
+ if (els.sbRunOpen) els.sbRunOpen.disabled = !sbRunUrl;
9770
+ if (els.sbRunUrl) els.sbRunUrl.textContent = sbRunUrl;
9771
+ return;
9772
+ }
9773
+ if (ev.type === 'sb-run-end') {
9774
+ sbRunRunning = false;
9775
+ if (els.sbRunStop) els.sbRunStop.hidden = true;
9776
+ }
9777
+ }
9778
+
9779
+ function sbRunExec(cmd) {
9780
+ if (!sbFolder) { toast('Önce repo indir ya da seç'); return; }
9781
+ if (sbRunRunning) { toast('Zaten çalışıyor — önce ■ ile durdur'); return; }
9782
+ sbRunRunning = true;
9783
+ if (els.sbRunStop) els.sbRunStop.hidden = false;
9784
+ beast.sandboxRun({ folder: sbFolder, cmd }).then((r) => {
9785
+ if (!r || !r.ok) {
9786
+ sbRunRunning = false;
9787
+ if (els.sbRunStop) els.sbRunStop.hidden = true;
9788
+ sbRunLine('[hata] ' + ((r && r.error) || 'çalıştırılamadı'), 't-err');
9789
+ }
9790
+ }).catch((e) => {
9791
+ sbRunRunning = false;
9792
+ if (els.sbRunStop) els.sbRunStop.hidden = true;
9793
+ sbRunLine('[hata] ' + String((e && e.message) || e), 't-err');
9794
+ });
9795
+ }
9796
+
9797
+ /* topbar + dock olay bağlama */
9798
+ if (els.sbBtn) els.sbBtn.addEventListener('click', () => setSandboxMode(!sbModeOn()));
9799
+ if (els.sbRepo) els.sbRepo.addEventListener('change', sbSelectCurrent);
9800
+ if (els.sbCloneBtn) els.sbCloneBtn.addEventListener('click', () => sbClone(els.sbCloneInput.value));
9801
+ if (els.sbCloneInput) {
9802
+ els.sbCloneInput.addEventListener('keydown', (e) => {
9803
+ if (e.key === 'Enter') {
9804
+ e.preventDefault();
9805
+ sbClone(els.sbCloneInput.value);
9806
+ }
9807
+ });
9808
+ }
9809
+ if (els.sbGhBtn) els.sbGhBtn.addEventListener('click', () => openGit());
9810
+ if (els.sbNew) els.sbNew.addEventListener('click', async () => {
9811
+ if (sbBusy) { toast('Mesaj sürüyor — önce ■ ile durdur'); return; }
9812
+ if (!sbFolder) return;
9813
+ const r = await beast.sandboxNew({ folder: sbFolder }).catch(() => null);
9814
+ if (r && r.ok) {
9815
+ sbSessionId = '';
9816
+ await sbSelectCurrent();
9817
+ sbLine('t-sys', 'yeni oturum — taze başlar');
9818
+ }
9819
+ });
9820
+ if (els.sbRemove) els.sbRemove.addEventListener('click', async () => {
9821
+ if (!sbFolder) { toast('Önce repo seç'); return; }
9822
+ const name = (sbItems.find((x) => x.folder === sbFolder) || {}).name || 'repo';
9823
+ const ok = await uiConfirm('"' + name + '" reposunu diskten silmek istiyor musun? (klasör + sohbet)', 'Sil');
9824
+ if (!ok) return;
9825
+ const r = await beast.sandboxRemove({ folder: sbFolder }).catch(() => null);
9826
+ if (r && r.ok) {
9827
+ sbFolder = '';
9828
+ sbSessionId = '';
9829
+ await loadSandboxList();
9830
+ } else {
9831
+ toast((r && r.error) || 'silinemedi');
9832
+ }
9833
+ });
9834
+ if (els.sbReveal) els.sbReveal.addEventListener('click', () => {
9835
+ if (sbFolder) beast.sandboxReveal({ folder: sbFolder }).catch(() => {});
9836
+ });
9837
+ if (els.sbInput) {
9838
+ els.sbInput.addEventListener('input', sbInputResize);
9839
+ els.sbInput.addEventListener('keydown', (e) => {
9840
+ if (e.key === 'Enter' && !e.shiftKey) {
9841
+ e.preventDefault();
9842
+ sbRunCurrent();
9843
+ }
9844
+ });
9845
+ }
9846
+ if (els.sbStop) {
9847
+ els.sbStop.addEventListener('click', () => {
9848
+ beast.sandboxStop({ folder: sbFolder }).then((r) => {
9849
+ if (r && r.ok && r.wasBusy === false) {
9850
+ sbSetBusy(false);
9851
+ sbStatusHide();
9852
+ }
9853
+ }).catch(() => {});
9854
+ });
9855
+ }
9856
+ /* ÇALIŞTIRICI butonları — KOMUT DEĞİL AJANA TALİMAT (komutu ajan bulur):
9857
+ uzun süreli süreci panel_run ile ÇALIŞTIR paneline bırakır, tur kilitlenmez */
9858
+ if (els.sbRunInstall) els.sbRunInstall.addEventListener('click', () =>
9859
+ sbInstruct('Bu projeyi KUR — paket bildirim dosyasına (package.json, requirements.txt, Cargo.toml, go.mod, pom.xml vb.) göre doğru kurulum komutunu bul ve çalıştır (run_command); sonucu 1-2 satırla raporla.')
9860
+ );
9861
+ if (els.sbRunStart) els.sbRunStart.addEventListener('click', () =>
9862
+ sbInstruct('Bu projeyi ÇALIŞTIR — README/package.json scripts\u2019e göre doğru başlatma komutunu bul; uzun süreli sunucu/süreçse panel_run ile ÇALIŞTIR panelinde başlat (tur kilitleme), adresini/çalıştığını kullanıcıya bildir.')
9863
+ );
9864
+ if (els.sbRunDev) els.sbRunDev.addEventListener('click', () =>
9865
+ sbInstruct('Bu projeyi GELİŞTİRME modunda başlat — package.json scripts\u2019e bak (npm run dev, vite, expo start, ng serve vb. hangisi varsa) ve panel_run ile ÇALIŞTIR panelinde başlat; adresini kullanıcıya bildir.')
9866
+ );
9867
+ if (els.sbRunGo) els.sbRunGo.addEventListener('click', () => {
9868
+ const c = els.sbRunCmd.value.trim();
9869
+ if (!c) { toast('Komut yaz'); return; }
9870
+ sbRunExec(c);
9871
+ });
9872
+ if (els.sbRunCmd) {
9873
+ els.sbRunCmd.addEventListener('keydown', (e) => {
9874
+ if (e.key === 'Enter') {
9875
+ e.preventDefault();
9876
+ const c = els.sbRunCmd.value.trim();
9877
+ if (c) sbRunExec(c);
9878
+ }
9879
+ });
9880
+ }
9881
+ if (els.sbRunStop) {
9882
+ els.sbRunStop.addEventListener('click', () => {
9883
+ beast.sandboxRunStop().then((r) => {
9884
+ if (r && r.ok) sbRunLine('⏹ durduruldu', 't-dim');
9885
+ }).catch(() => {});
9886
+ });
9887
+ }
9888
+ if (els.sbRunOpen) {
9889
+ els.sbRunOpen.addEventListener('click', () => {
9890
+ /* Tarayıcıda aç da AJANA TALİMAT: ajan çalışma adresini bulup kendi
9891
+ browser_open aracıyla dahili tarayıcıda açar */
9892
+ sbInstruct('Bu projenin web arayüzü çalışıyorsa adresini tespit et (çıktılar/README/port taraması) ve browser_open aracıyla DAHİLİ TARAYICIDA aç.')
9893
+ });
9894
+ }