beast-agent 2.41.0 → 2.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/assets/app.ico CHANGED
Binary file
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "beast-agent",
3
3
  "productName": "Beast Agent",
4
- "version": "2.41.0",
4
+ "version": "2.42.0",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
@@ -5030,7 +5030,7 @@ const skills = require('./skills');
5030
5030
  folder: r.folder,
5031
5031
  detected: info ? { kind: info.kind, label: info.label, install: info.install, run: info.run } : undefined,
5032
5032
  note:
5033
- 'Repo "' + r.name + '" → ' + r.folder + ' klasörüne indirildi. Kurmak için sandbox_repo action=kur, çalıştırmak için action=baslat; kullanıcı Sandbox panelindeki Kur/Başlat butonlarını da kullanabilir.',
5033
+ 'Repo "' + r.name + '" → ' + r.folder + ' klasörüne indirildi. Kurmak için sandbox_repo action=kur, çalıştırmak için action=baslat (bağımlılıklar eksikse önce kurar); kullanıcı Sandbox panelindeki Başlat/Dev butonlarını da kullanabilir.',
5034
5034
  };
5035
5035
  }
5036
5036
  if (action !== 'kur' && action !== 'baslat' && action !== 'durdur') {
@@ -131,9 +131,9 @@ function build(opts = {}) {
131
131
  ),
132
132
  prompt: [
133
133
  'BEAST SANDBOX MODUNDASIN: GitHub\u2019dan indirilmi\u015f A\u00c7IK KAYNAK bir repo klas\u00f6r\u00fcndesin (Beast-Sandbox). Bu kodun sahibi de\u011filsin \u2014 haz\u0131r bir d\u0131\u015f proje; repo sahibinin konvansiyonlar\u0131na uy, gereksiz yeniden bi\u00e7imlendirme yapma.',
134
- 'PANEL: \u00c7ALI\u015eTIR b\u00f6l\u00fcm\u00fcndeki Kur/Ba\u015flat/Dev butonlar\u0131 proje tipini OTOMAT\u0130K alg\u0131lar (package.json scripts + npm/pnpm/yarn/bun kilidi; requirements.txt/pyproject + uv; Cargo.toml; go.mod; statik index.html) ve do\u011fru komutu do\u011frudan \u00e7al\u0131\u015ft\u0131r\u0131r. \u00d6zel komut kutusu ad-hoc komut \u00e7al\u0131\u015ft\u0131r\u0131r; kaydet butonu o komutu repo i\u00e7in kal\u0131c\u0131 ba\u015flatma komutu yapar. Kullan\u0131c\u0131 butonla ba\u015flatt\u0131ysa \u00e7\u0131kt\u0131 panelde canl\u0131 akar, localhost adresi yakalan\u0131nca sa\u011fdaki dahili taray\u0131c\u0131da otomatik a\u00e7\u0131l\u0131r.',
135
- '\u0130\u015fe README + paket bildirim dosyalar\u0131yla ba\u015fla (list/glob); kullan\u0131c\u0131 Kur demediyse ba\u011f\u0131ml\u0131l\u0131klar\u0131 sen kurabilirsin (npm install, pip install -r requirements.txt vb.). Kur/Ba\u015flat ba\u015far\u0131s\u0131z olursa ger\u00e7ek hatay\u0131 g\u00f6rmek i\u00e7in ayn\u0131 komutu bash ile \u00e7al\u0131\u015ft\u0131r, nedenini bul, gerekiyorsa d\u00fczelt ve kullan\u0131c\u0131ya do\u011fru \u00f6zel komutu s\u00f6yle (kutuya yaz\u0131p kaydetsin).',
136
- 'UZUN S\u00dcREL\u0130 sunucu/s\u00fcre\u00e7 (npm start, npm run dev, python app.py vb.) bash ile ASLA ba\u015flatma \u2014 tur kilitlenir; kullan\u0131c\u0131 senden isterse panel_run ile ba\u015flat (\u00e7\u0131kt\u0131 panelde akar, adres sa\u011fda a\u00e7\u0131l\u0131r). Repo ba\u015f\u0131na TEK s\u00fcre\u00e7: kullan\u0131c\u0131 zaten ba\u015flatt\u0131ysa panel_run hata verir \u2014 kullan\u0131c\u0131ya \u00f6nce Durdur demesini s\u00f6yle.',
134
+ 'PANEL: \u00c7ALI\u015eTIR b\u00f6l\u00fcm\u00fcndeki Ba\u015flat/Dev butonlar\u0131 proje tipini OTOMAT\u0130K alg\u0131lar (package.json scripts + npm/pnpm/yarn/bun kilidi; requirements.txt/pyproject + uv; hugo/jekyll/mkdocs; Cargo.toml; go.mod; statik index.html) ve gerekirse \u00d6NCE ba\u011f\u0131ml\u0131l\u0131klar\u0131 kurup sonra do\u011fru komutu \u00e7al\u0131\u015ft\u0131r\u0131r. Alg\u0131lama komut bulamazsa ya da komut 10 sn i\u00e7inde hata verirse sistem sana OTOMAT\u0130K mesaj d\u00fc\u015f\u00fcr\u00fcr: repo yap\u0131s\u0131na g\u00f6re (README, betikler, site ayarlar\u0131, ana dosyalar) DO\u011eRU komutu bul ve panel_run ile ba\u015flat. \u00d6zel komut kutusu ad-hoc komut \u00e7al\u0131\u015ft\u0131r\u0131r; kaydet butonu o komutu repo i\u00e7in kal\u0131c\u0131 ba\u015flatma komutu yapar. Kullan\u0131c\u0131 butonla ba\u015flatt\u0131ysa \u00e7\u0131kt\u0131 panelde canl\u0131 akar, localhost adresi yakalan\u0131nca \u00c7ALI\u015eTIR panelinde CANLI \u00d6N\u0130ZLEME a\u00e7\u0131l\u0131r (kullan\u0131c\u0131 G\u00f6rsel/\u00c7\u0131kt\u0131 d\u00fc\u011fmesiyle ge\u00e7i\u015f yapar).',
135
+ '\u0130\u015fe README + paket bildirim dosyalar\u0131yla ba\u015fla (list/glob); ba\u011f\u0131ml\u0131l\u0131klar kurulu de\u011filse kurulumu sen yapabilirsin (npm install, pip install -r requirements.txt vb.) \u2014 Ba\u015flat/Dev butonlar\u0131 da gerekirse \u00f6nce kurar. Komut ba\u015far\u0131s\u0131z olursa ger\u00e7ek hatay\u0131 g\u00f6rmek i\u00e7in ayn\u0131 komutu bash ile \u00e7al\u0131\u015ft\u0131r, nedenini bul, gerekiyorsa d\u00fczelt ve kullan\u0131c\u0131ya do\u011fru \u00f6zel komutu s\u00f6yle (kutuya yaz\u0131p kaydetsin).',
136
+ 'UZUN S\u00dcREL\u0130 sunucu/s\u00fcre\u00e7 (npm start, npm run dev, hugo server, python app.py vb.) bash ile ASLA ba\u015flatma \u2014 tur kilitlenir; kullan\u0131c\u0131 senden isterse panel_run ile ba\u015flat (\u00e7\u0131kt\u0131 panelde akar, adres \u00c7ALI\u015eTIR panelinde canl\u0131 \u00f6nizlemede a\u00e7\u0131l\u0131r). Repo ba\u015f\u0131na TEK s\u00fcre\u00e7: kullan\u0131c\u0131 zaten ba\u015flatt\u0131ysa panel_run hata verir \u2014 kullan\u0131c\u0131ya \u00f6nce Durdur demesini s\u00f6yle.',
137
137
  ].join('\n'),
138
138
  mode: 'all',
139
139
  native: false,
@@ -1618,7 +1618,7 @@ const definitions = [
1618
1618
  function: {
1619
1619
  name: 'sandbox_repo',
1620
1620
  description:
1621
- 'Beast Sandbox yönetimi — kullanıcı GitHub repo adresi/linki verdiğinde ya da indirilmiş bir repoyu kurmak/çalıştırmak istediğinde kullan. action=indir: repoyu ~/Beast-Sandbox klasörüne klonlar (owner/repo ya da tam URL); action=kur: algılanan kurulum komutunu (npm install, pip install -r requirements.txt, uv sync, cargo fetch, go mod download) ÇALIŞTIR panelinde başlatır; action=baslat: algılanan çalıştırma komutunu başlatır (npm run dev, python app.py vb.) — localhost adresi yakalanınca sağdaki dahili tarayıcıda otomatik açılır; action=durdur: repodaki çalışan süreci durdurur. Kur/Başlat turu KİLİTLEMEZ, çıktı panelde canlı akar. repo alanı boşsa panelde son kullanılan repo kullanılır.',
1621
+ 'Beast Sandbox yönetimi — kullanıcı GitHub repo adresi/linki verdiğinde ya da indirilmiş bir repoyu kurmak/çalıştırmak istediğinde kullan. action=indir: repoyu ~/Beast-Sandbox klasörüne klonlar (owner/repo ya da tam URL); action=kur: algılanan kurulum komutunu (npm install, pip install -r requirements.txt, uv sync, cargo fetch, go mod download) ÇALIŞTIR panelinde başlatır; action=baslat: çalıştırma komutunu başlatır — bağımlılıklar eksikse önce kurar (npm install && npm run dev, python app.py vb.); localhost adresi yakalanınca ÇALIŞTIR panelinde canlı önizlemede açılır; action=durdur: repodaki çalışan süreci durdurur. Kur/Başlat/Dev turu KİLİTLEMEZ, çıktı panelde canlı akar. repo alanı boşsa panelde son kullanılan repo kullanılır.',
1622
1622
  parameters: {
1623
1623
  type: 'object',
1624
1624
  properties: {
package/src/main.js CHANGED
@@ -272,8 +272,15 @@ try {
272
272
  try { nodemailer = require('nodemailer'); } catch {}
273
273
 
274
274
  const APP_DIR = path.join(app.getPath('appData'), 'beast');
275
- /* Windows toast bildirimleri için AppUserModelID erken ayarlanmalı */
276
- try { app.setAppUserModelId('com.quantumalgo.beastagent'); } catch {}
275
+ /* Windows toast bildirimleri için AppUserModelID erken ayarlanmalı. ANCAK özel
276
+ AUMID, Windows'ta kayıtlı kısayol yoksa taskbar ikonunu electron.exe logosuna
277
+ düşürür. Bu yüzden yalnız paketli kurulumda ya da npm modunda masaüstü
278
+ kısayolu (aynı AUMID ile oluşturulur) mevcutsa atanır. */
279
+ try {
280
+ let aumidOk = app.isPackaged;
281
+ if (!aumidOk) aumidOk = fs.existsSync(path.join(app.getPath('desktop'), 'Beast Agent.lnk'));
282
+ if (aumidOk) app.setAppUserModelId('com.quantumalgo.beastagent');
283
+ } catch {}
277
284
  /* mem0-native embedding modeli whisper ile AYNI cache'i kullanır (src/agent/mem0.js okur) */
278
285
  process.env.BEAST_MODELS_DIR = path.join(APP_DIR, 'models');
279
286
 
@@ -3603,6 +3610,7 @@ function ensureDesktopShortcut() {
3603
3610
  description: 'Beast Agent — hızlı, hafif ve becerikli',
3604
3611
  icon: path.join(__dirname, '..', 'assets', 'app.ico'),
3605
3612
  iconIndex: 0,
3613
+ appUserModelId: 'com.quantumalgo.beastagent',
3606
3614
  });
3607
3615
  log.info('main', ok ? 'Masaüstü kısayolu oluşturuldu (npm modu)' : 'Masaüstü kısayolu oluşturulamadı');
3608
3616
  } catch (e) {
@@ -11152,6 +11160,7 @@ ipcMain.handle('sandbox:remove', async (_e, payload) => {
11152
11160
  delete sbCfg[folder];
11153
11161
  sbSaveCfg();
11154
11162
  }
11163
+ try { fs.rmSync(path.join(beastDir(), 'sandbox-installed', path.basename(folder)), { force: true }); } catch {}
11155
11164
  return { ok: true };
11156
11165
  });
11157
11166
 
@@ -11211,7 +11220,7 @@ ipcMain.handle('sandbox:tree', (_e, payload) => {
11211
11220
  });
11212
11221
 
11213
11222
  /* Sandbox ÇALIŞTIRICI v2: repo başına YÖNETİLEN SÜREÇLER.
11214
- Kur/Başlat/Durdur butonları artık deterministik: proje tipi algılanır
11223
+ Başlat/Dev/Durdur butonları artık deterministik: proje tipi algılanır
11215
11224
  (package.json, requirements.txt, pyproject.toml, Cargo.toml, go.mod,
11216
11225
  index.html) ve doğru komut DOĞRUDAN çalıştırılır. Her repo kendi süreç
11217
11226
  yuvasında koşar — birden çok repo aynı anda çalışabilir; çıktı tamponlanır,
@@ -11253,6 +11262,25 @@ function sbPythonCmd() {
11253
11262
  return 'python';
11254
11263
  }
11255
11264
 
11265
+ /* statik site üreticileri: hugo / jekyll / mkdocs — doküman repoları sık böyle;
11266
+ Başlat/Dev butonları bunları da tanısın (ör. docs/ altında hugo.yaml) */
11267
+ function sbSiteGenInfo(folder) {
11268
+ for (const d of ['', 'docs', 'site', 'website']) {
11269
+ const base = d ? path.join(folder, d) : folder;
11270
+ const has = (f) => { try { return fs.existsSync(path.join(base, f)); } catch { return false; } };
11271
+ if (has('hugo.toml') || has('hugo.yaml') || has('hugo.json') || (has('config.toml') && (has('content') || has('themes')))) {
11272
+ return { kind: 'static', label: 'hugo', install: '', run: 'hugo server' + (d ? ' -s ' + d : '') };
11273
+ }
11274
+ if (has('_config.yml') && (has('Gemfile') || has('Gemfile.lock'))) {
11275
+ return { kind: 'ruby', label: 'jekyll', install: 'bundle install', run: 'bundle exec jekyll serve' + (d ? ' -s ' + d : '') };
11276
+ }
11277
+ if (has('mkdocs.yml') || has('mkdocs.yaml')) {
11278
+ return { kind: 'python', label: 'mkdocs', install: sbPythonCmd() + ' -m pip install mkdocs', run: sbPythonCmd() + ' -m mkdocs serve' };
11279
+ }
11280
+ }
11281
+ return null;
11282
+ }
11283
+
11256
11284
  /* repo köküne bakıp proje tipini + kur/çalıştır komutlarını çıkarır */
11257
11285
  function sbProjectInfo(folder) {
11258
11286
  const out = { kind: 'unknown', label: 'bilinmiyor', name: path.basename(folder), install: '', run: '', dev: '', build: '' };
@@ -11275,6 +11303,21 @@ function sbProjectInfo(folder) {
11275
11303
  out.build = s.build ? pm + ' run build' : '';
11276
11304
  return out;
11277
11305
  }
11306
+ /* statik site üreticisi (hugo/jekyll/mkdocs) python paketinden ÖNCE gelir:
11307
+ docs/ altındaki hugo.yaml, kökteki requirements.txt'i gölgeleyebilsin */
11308
+ const site = sbSiteGenInfo(folder);
11309
+ if (site) {
11310
+ out.kind = site.kind;
11311
+ out.label = site.label;
11312
+ if (site.install) out.install = site.install;
11313
+ if (site.run) { out.run = site.run; out.dev = site.run; }
11314
+ if (!out.install) {
11315
+ const py = sbPythonCmd();
11316
+ if (fs.existsSync(path.join(folder, 'requirements.txt'))) out.install = py + ' -m pip install -r requirements.txt';
11317
+ else if (fs.existsSync(path.join(folder, 'pyproject.toml'))) out.install = py + ' -m pip install -e .';
11318
+ }
11319
+ return out;
11320
+ }
11278
11321
  const hasPy = ['pyproject.toml', 'requirements.txt', 'setup.py'].some((f) => fs.existsSync(path.join(folder, f)));
11279
11322
  if (hasPy) {
11280
11323
  const py = sbPythonCmd();
@@ -11350,7 +11393,7 @@ function sbSlotPush(slot, folder, text) {
11350
11393
  sbRunEmit(folder, text, slot.kind);
11351
11394
  }
11352
11395
 
11353
- function sbRunStartManaged(folder, cmd, kind) {
11396
+ function sbRunStartManaged(folder, cmd, kind, opts) {
11354
11397
  folder = String(folder || '');
11355
11398
  cmd = String(cmd || '').trim();
11356
11399
  kind = kind === 'install' ? 'install' : 'run';
@@ -11365,7 +11408,7 @@ function sbRunStartManaged(folder, cmd, kind) {
11365
11408
  windowsHide: true,
11366
11409
  env: { ...process.env, FORCE_COLOR: '0', NO_COLOR: '1' },
11367
11410
  });
11368
- const slot = { proc, kind, cmd, url: '', lines: [], startedAt: Date.now() };
11411
+ const slot = { proc, kind, cmd, url: '', lines: [], startedAt: Date.now(), installCmd: (opts && opts.installCmd) || '' };
11369
11412
  sbProcs.set(folder, slot);
11370
11413
  const onLine = (buf) => {
11371
11414
  for (const piece of String(buf).split(/\r?\n/)) {
@@ -11387,9 +11430,20 @@ function sbRunStartManaged(folder, cmd, kind) {
11387
11430
  proc.on('close', (code) => {
11388
11431
  sbSlotPush(slot, folder, '[süreç bitti' + (code != null ? ' — kod ' + code : '') + ']');
11389
11432
  slot.proc = null;
11433
+ /* başarılı kurulum damgalanır: sonraki Başlat/Dev kurulumu tekrarlamaz */
11434
+ if (code === 0 && (slot.kind === 'install' || slot.installCmd)) sbMarkInstalled(folder);
11435
+ /* hızlı çöküş (10 sn içinde sıfırdan farklı çıkış): renderer bunu ajan
11436
+ devralması için işaret sayar — yanlış algılanan komut ajanla düzeltilir */
11437
+ const quickFail = code != null && Number(code) !== 0 && Date.now() - slot.startedAt < 10000;
11390
11438
  try {
11391
11439
  if (win && !win.isDestroyed()) {
11392
- win.webContents.send('agent:event', { type: 'sb-run-end', folder, code: code == null ? null : Number(code) });
11440
+ win.webContents.send('agent:event', {
11441
+ type: 'sb-run-end',
11442
+ folder,
11443
+ code: code == null ? null : Number(code),
11444
+ kind: slot.kind,
11445
+ quickFail,
11446
+ });
11393
11447
  }
11394
11448
  } catch {}
11395
11449
  });
@@ -11446,7 +11500,35 @@ function sbInstallRepo(folder) {
11446
11500
 
11447
11501
  ipcMain.handle('sandbox:install', async (_e, payload) => sbInstallRepo(String((payload && payload.folder) || '')));
11448
11502
 
11449
- /* Başlat/Dev: algılanan/kayıtlı çalıştırma komutunu başlatır (panel + ajan ortak) */
11503
+ /* bağımlılıklar eksik mi? — Başlat/Dev gerekirse ÖNCE kurar; kurulum bir kez
11504
+ yapıldıktan sonra damga dosyasıyla (node_modules var / %APPDATA% damgası)
11505
+ tekrarlanmaz */
11506
+ function sbInstallNeeded(folder, info) {
11507
+ try {
11508
+ if (!info.install) return false;
11509
+ if (info.kind === 'node') {
11510
+ const nm = path.join(folder, 'node_modules');
11511
+ return !fs.existsSync(nm) || !fs.readdirSync(nm).length;
11512
+ }
11513
+ if (info.label === 'jekyll') return !fs.existsSync(path.join(folder, 'Gemfile.lock'));
11514
+ if (info.kind === 'python' || info.label === 'mkdocs') {
11515
+ if (fs.existsSync(path.join(folder, '.venv'))) return false;
11516
+ return !fs.existsSync(path.join(beastDir(), 'sandbox-installed', path.basename(folder)));
11517
+ }
11518
+ } catch {}
11519
+ return false;
11520
+ }
11521
+
11522
+ function sbMarkInstalled(folder) {
11523
+ try {
11524
+ const dir = path.join(beastDir(), 'sandbox-installed');
11525
+ fs.mkdirSync(dir, { recursive: true });
11526
+ fs.writeFileSync(path.join(dir, path.basename(folder)), new Date().toISOString());
11527
+ } catch {}
11528
+ }
11529
+
11530
+ /* Başlat/Dev: algılanan çalıştırma komutunu başlatır; bağımlılıklar eksikse
11531
+ önce kurulum komutunu zincirler (npm install && npm run dev gibi) */
11450
11532
  function sbStartRepo(folder, mode) {
11451
11533
  folder = String(folder || '');
11452
11534
  mode = String(mode || 'run') === 'dev' ? 'dev' : 'run';
@@ -11454,8 +11536,10 @@ function sbStartRepo(folder, mode) {
11454
11536
  const info = sbProjectInfoMerged(folder);
11455
11537
  const cmd = mode === 'dev' ? info.dev || info.run : info.run || info.dev;
11456
11538
  if (!cmd) return { ok: false, error: 'Bu repo için çalıştırma komutu algılanamadı — özel komutu kutuya yazıp ▶ ile çalıştır' };
11457
- const r = sbRunStartManaged(folder, cmd, 'run');
11458
- if (r.ok) r.cmd = cmd;
11539
+ const pre = sbInstallNeeded(folder, info) ? info.install : '';
11540
+ const full = pre ? pre + ' && ' + cmd : cmd;
11541
+ const r = sbRunStartManaged(folder, full, 'run', pre ? { installCmd: pre } : null);
11542
+ if (r.ok) r.cmd = full;
11459
11543
  return r;
11460
11544
  }
11461
11545
 
@@ -11524,6 +11608,98 @@ ipcMain.handle('sandbox:openurl', async (_e, url) => {
11524
11608
  }
11525
11609
  });
11526
11610
 
11611
+ /* ---------- SANDBOX PANEL İÇİ CANLI ÖNİZLEME (native WebContentsView) ----------
11612
+ ÇALIŞTIR bölümünde, çıktı günlüğünün yerine repo'nun çalışan hâlini gösterir.
11613
+ Tarayıcı dock'undan bağımsızdır: renderer host alanının DOM dikdörtgenini
11614
+ gönderir, view tam o alana oturur. Yalnız localhost adresleri yüklenir. */
11615
+ const sbPreview = { view: null, attached: false, visible: false, url: '', bounds: null };
11616
+
11617
+ function ensureSbPreview() {
11618
+ if (sbPreview.view) return sbPreview.view;
11619
+ const view = new WebContentsView({
11620
+ webPreferences: {
11621
+ partition: 'persist:sandbox-preview',
11622
+ contextIsolation: true,
11623
+ nodeIntegration: false,
11624
+ sandbox: true,
11625
+ spellcheck: false,
11626
+ backgroundThrottling: false,
11627
+ },
11628
+ });
11629
+ view.setBackgroundColor(settings.theme === 'dark' ? '#0d0d0f' : '#ffffff');
11630
+ const wc = view.webContents;
11631
+ try {
11632
+ const chromeUA = `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${process.versions.chrome} Safari/537.36`;
11633
+ wc.setUserAgent(chromeUA);
11634
+ } catch {}
11635
+ wc.setWindowOpenHandler(({ url }) => {
11636
+ if (/^https?:\/\//i.test(url)) wc.loadURL(url).catch(() => {});
11637
+ return { action: 'deny' };
11638
+ });
11639
+ wc.on('render-process-gone', () => {
11640
+ if (win && !win.isDestroyed() && sbPreview.view) {
11641
+ try { win.contentView.removeChildView(sbPreview.view); } catch {}
11642
+ }
11643
+ sbPreview.view = null;
11644
+ sbPreview.attached = false;
11645
+ sbPreview.visible = false;
11646
+ });
11647
+ sbPreview.view = view;
11648
+ return view;
11649
+ }
11650
+
11651
+ function sbPreviewDetach() {
11652
+ if (sbPreview.view && win && !win.isDestroyed()) {
11653
+ try { sbPreview.view.setVisible(false); } catch {}
11654
+ try { win.contentView.removeChildView(sbPreview.view); } catch {}
11655
+ }
11656
+ sbPreview.attached = false;
11657
+ sbPreview.visible = false;
11658
+ }
11659
+
11660
+ function sbPreviewSet(payload) {
11661
+ if (!win || win.isDestroyed()) return { ok: false, error: 'pencere yok' };
11662
+ const p = payload || {};
11663
+ if (p.bounds && typeof p.bounds === 'object') {
11664
+ sbPreview.bounds = {
11665
+ x: Math.max(0, Math.round(Number(p.bounds.x) || 0)),
11666
+ y: Math.max(0, Math.round(Number(p.bounds.y) || 0)),
11667
+ width: Math.max(0, Math.round(Number(p.bounds.width) || 0)),
11668
+ height: Math.max(0, Math.round(Number(p.bounds.height) || 0)),
11669
+ };
11670
+ }
11671
+ if (p.url) {
11672
+ const u = String(p.url);
11673
+ if (/^https?:\/\/(?:localhost|127\.0\.0\.1)(?::\d{2,5})?(?:[/?#]|$)/i.test(u)) sbPreview.url = u;
11674
+ }
11675
+ const b = sbPreview.bounds;
11676
+ const want = p.visible !== false && !!sbPreview.url && !!b && b.width > 0 && b.height > 0;
11677
+ if (!want) {
11678
+ if (sbPreview.view) {
11679
+ try { sbPreview.view.setVisible(false); } catch {}
11680
+ }
11681
+ sbPreview.visible = false;
11682
+ return { ok: true, visible: false };
11683
+ }
11684
+ const view = ensureSbPreview();
11685
+ if (sbPreview.url) {
11686
+ let cur = '';
11687
+ try { cur = view.webContents.getURL(); } catch {}
11688
+ if (cur !== sbPreview.url) view.webContents.loadURL(sbPreview.url).catch(() => {});
11689
+ }
11690
+ if (!sbPreview.attached) {
11691
+ try { win.contentView.addChildView(view); sbPreview.attached = true; } catch {}
11692
+ }
11693
+ try {
11694
+ view.setBounds({ x: b.x, y: b.y, width: b.width, height: b.height });
11695
+ view.setVisible(true);
11696
+ sbPreview.visible = true;
11697
+ } catch {}
11698
+ return { ok: true, visible: true, url: sbPreview.url };
11699
+ }
11700
+
11701
+ ipcMain.handle('sbpreview:set', async (_e, payload) => sbPreviewSet(payload || {}));
11702
+
11527
11703
  /* Sağ tık menüsü: HTML dosyasını dahili tarayıcıda GÖRÜNÜR aç */
11528
11704
  /* ---------- BC DAHİLİ STATİK SUNUCU ----------
11529
11705
  Beast Code çıktısı ASLA file:// ile açılmaz: ES modülleri, fetch, ServiceWorker
package/src/preload.js CHANGED
@@ -192,6 +192,7 @@ contextBridge.exposeInMainWorld('beast', {
192
192
  sandboxProcState: (payload) => ipcRenderer.invoke('sandbox:procstate', payload),
193
193
  sandboxCfgSet: (payload) => ipcRenderer.invoke('sandbox:cfg:set', payload),
194
194
  sandboxOpenUrl: (url) => ipcRenderer.invoke('sandbox:openurl', url),
195
+ sbPreviewSet: (payload) => ipcRenderer.invoke('sbpreview:set', payload),
195
196
  waStart: () => ipcRenderer.invoke('wa:start'),
196
197
  waStop: () => ipcRenderer.invoke('wa:stop'),
197
198
  waReset: () => ipcRenderer.invoke('wa:reset'),
@@ -419,20 +419,24 @@
419
419
  <div id="sbStatus" hidden></div>
420
420
  <div id="sbInputRow">
421
421
  <textarea id="sbInput" rows="1" spellcheck="false" autocomplete="off" placeholder="Sandbox mesajı — ajan bu repo klasöründe çalışır…"></textarea>
422
+ <button id="sbSend" title="Gönder (Enter)"><svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg></button>
422
423
  <button id="sbStop" title="Çalışan mesajı durdur" hidden data-i18n-title="bc_stop"><svg viewBox="0 0 24 24" width="13" height="13" fill="currentColor" stroke="none"><rect x="6" y="6" width="12" height="12" rx="2"/></svg></button>
423
424
  </div>
424
425
  </section>
425
426
 
427
+ <div id="sbSplit" title="Sürükleyerek boyutlandır"></div>
428
+
426
429
  <section id="sbRun">
427
430
  <div id="sbRunHead">
428
431
  <span id="sbRunTitle">ÇALIŞTIR</span>
429
432
  <span id="sbRunKind"></span>
430
433
  <span id="sbRunCwd" title="">—</span>
434
+ <span class="file-spacer"></span>
435
+ <button id="sbRunView" class="on" title="Görsel önizleme açık — çıktıya dön">Görsel</button>
431
436
  </div>
432
437
  <div id="sbRunBtns">
433
- <button id="sbRunInstall" title="Kur — algılanan paket yöneticisiyle bağımlılıkları yükle (npm/pip/cargo/go)"><svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:3px"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>Kur</button>
434
- <button id="sbRunStart" title="Başlat — algılanan komutla çalıştır (sunucu adresi sağdaki tarayıcıda açılır)"><svg viewBox="0 0 24 24" width="12" height="12" fill="currentColor" stroke="none" style="vertical-align:-2px;margin-right:3px"><polygon points="6 3 20 12 6 21 6 3"/></svg>Başlat</button>
435
- <button id="sbRunDev" title="Geliştirme modunda başlat (npm run dev, vite, expo…)"><svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:3px"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>Dev</button>
438
+ <button id="sbRunStart" title="Başlat — gerekirse bağımlılıkları kurar, sonra çalıştırır (adres panelde canlı önizlemede açılır)"><svg viewBox="0 0 24 24" width="12" height="12" fill="currentColor" stroke="none" style="vertical-align:-2px;margin-right:3px"><polygon points="6 3 20 12 6 21 6 3"/></svg>Başlat</button>
439
+ <button id="sbRunDev" title="Geliştirme modunda başlat (gerekirse önce kurar)"><svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:3px"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>Dev</button>
436
440
  <button id="sbRunStop" title="Süreci durdur" hidden><svg viewBox="0 0 24 24" width="12" height="12" fill="currentColor" stroke="none" style="vertical-align:-2px;margin-right:3px"><rect x="6" y="6" width="12" height="12" rx="2"/></svg>Durdur</button>
437
441
  </div>
438
442
  <div id="sbRunCustom">
@@ -440,6 +444,7 @@
440
444
  <button id="sbRunGo" title="Çalıştır"><svg viewBox="0 0 24 24" width="12" height="12" fill="currentColor" stroke="none"><polygon points="6 3 20 12 6 21 6 3"/></svg></button>
441
445
  <button id="sbRunSave" title="Yazılan komutu bu reponun başlatma komutu olarak kaydet"><svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg></button>
442
446
  </div>
447
+ <div id="sbPreviewHost" hidden><div id="sbPreviewHint">🌐 canlı önizleme — Başlat/Dev ile sunucu açılınca burada görünür</div></div>
443
448
  <div id="sbRunOut"></div>
444
449
  <div id="sbRunFoot">
445
450
  <button id="sbRunOpen" title="Önizlemeyi sağdaki dahili tarayıcıda aç" disabled><svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:3px"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg><span id="sbRunOpenLabel">Tarayıcıda aç</span></button>
@@ -135,9 +135,12 @@ const els = {
135
135
  sbStatus: $('#sbStatus'),
136
136
  sbInput: $('#sbInput'),
137
137
  sbStop: $('#sbStop'),
138
+ sbSend: $('#sbSend'),
139
+ sbSplit: $('#sbSplit'),
140
+ sbRun: $('#sbRun'),
138
141
  sbRunCwd: $('#sbRunCwd'),
142
+ sbRunView: $('#sbRunView'),
139
143
  sbRunKind: $('#sbRunKind'),
140
- sbRunInstall: $('#sbRunInstall'),
141
144
  sbRunStart: $('#sbRunStart'),
142
145
  sbRunDev: $('#sbRunDev'),
143
146
  sbRunStop: $('#sbRunStop'),
@@ -6000,6 +6003,7 @@ function onEvent(ev) {
6000
6003
  /* IDE modunda tarayıcı açılır/kapanır/genişlik değişirse row yeniden bölünür —
6001
6004
  editör + Beast Code ORTAK kırpılıp ORTAK açılır (kullanıcı payı korunur) */
6002
6005
  if (ideModeOn()) ideSplitApplyFrac();
6006
+ if (sbModeOn()) sbSplitApplyFrac();
6003
6007
  if (shown) {
6004
6008
  els.browserBtn.classList.add('on');
6005
6009
  if (ev.url && document.activeElement !== els.bbUrl) {
@@ -11967,6 +11971,10 @@ const sbTreeState = { cache: new Map(), open: new Set(['']), path: '' };
11967
11971
  let sbRunUrl = '';
11968
11972
  let sbRunRunning = false;
11969
11973
  let sbRunInfo = null;
11974
+ let sbAutoAssist = null; /* butonla başlatılan süreç hızlı çökerse: 'install' | 'run' */
11975
+ let sbPreviewOn = true; /* ÇALIŞTIR içi görsel önizleme tercihi */
11976
+ let sbPreviewShown = false;
11977
+ let sbPreviewSuspend = false; /* ayırıcı sürüklenirken native view gizli tutulur */
11970
11978
 
11971
11979
  function sbModeOn() {
11972
11980
  return document.body.classList.contains('sandbox-mode');
@@ -12168,6 +12176,7 @@ async function sbSelectCurrent() {
12168
12176
  sbRunLine('ℹ ' + (det.info.label || 'proje') + ' · kur: ' + (det.info.install || '—') + ' · çalıştır: ' + (det.info.run || '—'), 't-dim');
12169
12177
  }
12170
12178
  }
12179
+ sbPreviewApply();
12171
12180
  /* sol konsol: seçilen reponun klasörü — oturum açıldıktan sonra da garanti tazele */
12172
12181
  sbTreeState.cache.clear();
12173
12182
  if (sbModeOn()) renderFileTree().catch(() => {});
@@ -12183,7 +12192,8 @@ function sbInputResize() {
12183
12192
 
12184
12193
  function sbRunCurrent() {
12185
12194
  const msg = els.sbInput.value.trim();
12186
- if (!msg || !sbFolder || sbCloning) return;
12195
+ if (!msg || sbCloning) return;
12196
+ if (!sbFolder) { toast('Önce repo indir ya da seç'); return; }
12187
12197
  els.sbInput.value = '';
12188
12198
  sbInputResize();
12189
12199
  sbLine('t-cmd', 'sen> ' + msg);
@@ -12254,6 +12264,7 @@ function setSandboxMode(on) {
12254
12264
  if (on && financeModeOn()) { setFinanceMode(false); }
12255
12265
  document.body.classList.toggle('sandbox-mode', !!on);
12256
12266
  if (els.sbBtn) els.sbBtn.classList.toggle('on', !!on);
12267
+ sbPreviewApply();
12257
12268
  /* marka + sol konsol başlığı: moduna göre BEAST Agent/Code/Studio/Sandbox */
12258
12269
  const brandSub = document.querySelector('#brand .brand-sub');
12259
12270
  if (brandSub) brandSub.textContent = on ? 'Sandbox' : 'Agent';
@@ -12261,6 +12272,7 @@ function setSandboxMode(on) {
12261
12272
  if (ftp && on) ftp.textContent = 'REPO DOSYALARI';
12262
12273
  if (on) {
12263
12274
  loadSandboxList();
12275
+ sbSplitRestore();
12264
12276
  sbTreeState.cache.clear();
12265
12277
  renderFileTree().catch(() => {});
12266
12278
  } else if (!ideModeOn() && !studioModeOn()) {
@@ -12272,17 +12284,20 @@ function setSandboxMode(on) {
12272
12284
 
12273
12285
  /* ---------- ÇALIŞTIRICI v2: butonlar DETERMİNİSTİK ----------
12274
12286
  Proje tipi algılanır (package.json/requirements.txt/pyproject/Cargo.toml/
12275
- go.mod/index.html) ve Kur/Başlat/Dev butonları algılanan komutu DOĞRUDAN
12276
- çalıştırır; süreç panelde canlı akar, repo başına ayrı yuvada koşar. */
12287
+ go.mod/hugo/jekyll/mkdocs/index.html); Başlat/Dev gerekirse bağımlılıkları
12288
+ ÖNCE kurar (npm install && npm run dev gibi), süreç panelde canlı akar. */
12277
12289
  async function sbRunLaunch(mode) {
12278
12290
  if (!sbFolder) { toast('Önce repo indir ya da seç'); return; }
12279
12291
  if (sbRunRunning) { toast('Bu repoda süreç çalışıyor — önce ■ Durdur'); return; }
12280
- const call = mode === 'install'
12281
- ? beast.sandboxInstall({ folder: sbFolder })
12282
- : beast.sandboxStart({ folder: sbFolder, mode: mode === 'dev' ? 'dev' : 'run' });
12283
- const r = await call.catch((e) => ({ ok: false, error: String((e && e.message) || e) }));
12292
+ sbAutoAssist = mode;
12293
+ const r = await beast.sandboxStart({ folder: sbFolder, mode: mode === 'dev' ? 'dev' : 'run' })
12294
+ .catch((e) => ({ ok: false, error: String((e && e.message) || e) }));
12284
12295
  if (!r || !r.ok) {
12285
- sbRunLine('[hata] ' + ((r && r.error) || 'başlatılamadı'), 't-err');
12296
+ sbAutoAssist = null;
12297
+ const err = (r && r.error) || 'başlatılamadı';
12298
+ sbRunLine('[hata] ' + err, 't-err');
12299
+ /* algılama komut bulamadıysa ajan devralır: repo yapısından doğru komutu bulur */
12300
+ if (/algılanamadı/.test(err)) sbAskAgent(mode);
12286
12301
  return;
12287
12302
  }
12288
12303
  sbRunRunning = true;
@@ -12332,24 +12347,97 @@ function sbRunIngest(ev) {
12332
12347
  if (els.sbRunOpen) els.sbRunOpen.disabled = !sbRunUrl;
12333
12348
  if (els.sbRunUrl) els.sbRunUrl.textContent = sbRunUrl;
12334
12349
  sbBadge(folder || sbFolder, true);
12335
- /* sağdaki dahili tarayıcıda otomatik açılır — sandbox modundayken */
12336
- if (sbModeOn() && sbRunUrl) beast.sandboxOpenUrl(sbRunUrl).catch(() => {});
12350
+ /* ÇALIŞTIR panelinde canlı önizleme otomatik açılır */
12351
+ sbPreviewApply();
12337
12352
  return;
12338
12353
  }
12339
12354
  if (ev.type === 'sb-run-end') {
12340
12355
  sbRunRunning = false;
12341
12356
  if (els.sbRunStop) els.sbRunStop.hidden = true;
12342
12357
  sbBadge(folder || sbFolder, false);
12358
+ const assist = sbAutoAssist;
12359
+ sbAutoAssist = null;
12360
+ if (ev.quickFail && assist) {
12361
+ const kind = ev.kind === 'install' ? 'install' : 'run';
12362
+ sbRunLine('[hata] komut başarısız bitti — ajan doğru komutu arıyor…', 't-err');
12363
+ sbAskAgent(kind);
12364
+ }
12365
+ }
12366
+ }
12367
+
12368
+ /* ---------- ÇALIŞTIR içi CANLI ÖNİZLEME: DOM dikdörtgenini main'e bildir ---------- */
12369
+ function sbPreviewLayout() {
12370
+ if (!sbPreviewShown || sbPreviewSuspend) return;
12371
+ const host = document.getElementById('sbPreviewHost');
12372
+ if (!host) return;
12373
+ const r = host.getBoundingClientRect();
12374
+ if (!r.width || !r.height) return;
12375
+ beast.sbPreviewSet({
12376
+ url: sbRunUrl,
12377
+ visible: true,
12378
+ bounds: { x: r.left, y: r.top, width: r.width, height: r.height },
12379
+ }).catch(() => {});
12380
+ }
12381
+
12382
+ function sbPreviewApply() {
12383
+ const show = sbPreviewOn && sbModeOn() && !!sbFolder && !!sbRunUrl && !sbPreviewSuspend;
12384
+ sbPreviewShown = show;
12385
+ const host = document.getElementById('sbPreviewHost');
12386
+ if (host) host.hidden = !show;
12387
+ if (els.sbRunOut) els.sbRunOut.hidden = show;
12388
+ if (show) {
12389
+ sbPreviewLayout();
12390
+ /* görünür olunca yerleşim oturana kadar birkaç kez tazele */
12391
+ setTimeout(sbPreviewLayout, 50);
12392
+ setTimeout(sbPreviewLayout, 250);
12393
+ } else {
12394
+ beast.sbPreviewSet({ visible: false }).catch(() => {});
12395
+ }
12396
+ }
12397
+
12398
+ function sbPreviewToggle() {
12399
+ sbPreviewOn = !sbPreviewOn;
12400
+ if (els.sbRunView) {
12401
+ els.sbRunView.classList.toggle('on', sbPreviewOn);
12402
+ els.sbRunView.textContent = sbPreviewOn ? 'Görsel' : 'Çıktı';
12403
+ els.sbRunView.title = sbPreviewOn ? 'Görsel önizleme açık — çıktıya dön' : 'Çıktı görünümü — görsele dön';
12343
12404
  }
12405
+ sbPreviewApply();
12406
+ }
12407
+
12408
+ /* Başlat/Dev başarısız olunca ajanı devreye sokar: repo yapısına göre
12409
+ DOĞRU komutu bulup panel_run ile başlatmasını ister (otomatik devralma). */
12410
+ function sbAskAgent(kind) {
12411
+ if (!sbFolder) return;
12412
+ const repoName = (sbItems.find((x) => x.folder === sbFolder) || {}).name || 'repo';
12413
+ const msg = kind === 'install'
12414
+ ? `Kur butonu bu repoda başarısız oldu. ${repoName} reposunun dosyalarını incele (README, requirements.txt, package.json, pyproject.toml, Gemfile, mkdocs/hugo ayarları, alt klasörler) ve DOĞRU kurulum komutunu bulup panel_run ile ÇALIŞTIR panelinde çalıştır. Sonucu tek cümleyle bildir.`
12415
+ : `Başlat/Dev butonu bu repoda başarısız oldu. ${repoName} reposunun yapısını incele (README, package.json scripts, hugo/mkdocs/jekyll ayarları, main/app.py, Cargo.toml, go.mod…) ve GERÇEK çalıştırma komutunu bulup panel_run ile ÇALIŞTIR panelinde başlat. Web adresi çıkarsa kullanıcıya bildir.`;
12416
+ sbLine('t-sys', '⚙ ajan devrede — doğru komutu bulup başlatacak…');
12417
+ sbSetBusy(true);
12418
+ beast.sandboxSend({ folder: sbFolder, msg }).then((r) => {
12419
+ if (r && r.ok) {
12420
+ if (r.sessionId) sbSessionId = r.sessionId;
12421
+ if (r.queued) sbLine('t-dim', '⏳ kuyrukta (' + r.count + ') — iş bitince gönderilir');
12422
+ } else {
12423
+ sbSetBusy(false);
12424
+ sbLine('t-err', (r && r.error) || 'gönderilemedi');
12425
+ }
12426
+ }).catch((e) => {
12427
+ sbSetBusy(false);
12428
+ sbLine('t-err', String((e && e.message) || e));
12429
+ });
12344
12430
  }
12345
12431
 
12346
12432
  function sbRunExec(cmd) {
12347
12433
  if (!sbFolder) { toast('Önce repo indir ya da seç'); return; }
12348
12434
  if (sbRunRunning) { toast('Zaten çalışıyor — önce ■ ile durdur'); return; }
12349
12435
  sbRunRunning = true;
12436
+ sbAutoAssist = 'run';
12350
12437
  if (els.sbRunStop) els.sbRunStop.hidden = false;
12351
12438
  beast.sandboxRun({ folder: sbFolder, cmd }).then((r) => {
12352
12439
  if (!r || !r.ok) {
12440
+ sbAutoAssist = null;
12353
12441
  sbRunRunning = false;
12354
12442
  if (els.sbRunStop) els.sbRunStop.hidden = true;
12355
12443
  sbRunLine('[hata] ' + ((r && r.error) || 'çalıştırılamadı'), 't-err');
@@ -12357,6 +12445,7 @@ function sbRunExec(cmd) {
12357
12445
  sbBadge(sbFolder, true);
12358
12446
  }
12359
12447
  }).catch((e) => {
12448
+ sbAutoAssist = null;
12360
12449
  sbRunRunning = false;
12361
12450
  if (els.sbRunStop) els.sbRunStop.hidden = true;
12362
12451
  sbRunLine('[hata] ' + String((e && e.message) || e), 't-err');
@@ -12412,6 +12501,10 @@ if (els.sbInput) {
12412
12501
  }
12413
12502
  });
12414
12503
  }
12504
+ if (els.sbSend) els.sbSend.addEventListener('click', () => {
12505
+ sbRunCurrent();
12506
+ if (els.sbInput) els.sbInput.focus();
12507
+ });
12415
12508
  if (els.sbStop) {
12416
12509
  els.sbStop.addEventListener('click', () => {
12417
12510
  beast.sandboxStop({ folder: sbFolder }).then((r) => {
@@ -12422,8 +12515,7 @@ if (els.sbStop) {
12422
12515
  }).catch(() => {});
12423
12516
  });
12424
12517
  }
12425
- /* ÇALIŞTIRICI butonları: algılanan komutu doğrudan çalıştırır */
12426
- if (els.sbRunInstall) els.sbRunInstall.addEventListener('click', () => sbRunLaunch('install'));
12518
+ /* ÇALIŞTIRICI butonları: Başlat/Dev algılanan komutu (gerekirse kurup) çalıştırır */
12427
12519
  if (els.sbRunStart) els.sbRunStart.addEventListener('click', () => sbRunLaunch('run'));
12428
12520
  if (els.sbRunDev) els.sbRunDev.addEventListener('click', () => sbRunLaunch('dev'));
12429
12521
  if (els.sbRunGo) els.sbRunGo.addEventListener('click', () => {
@@ -12475,3 +12567,75 @@ if (els.sbRunOpen) {
12475
12567
  if (!r || !r.ok) toast('Açılamadı: ' + ((r && r.error) || 'bilinmeyen hata'));
12476
12568
  });
12477
12569
  }
12570
+ if (els.sbRunView) els.sbRunView.addEventListener('click', sbPreviewToggle);
12571
+
12572
+ /* görsel önizleme boyutu her yerleşim değişiminde main'e bildirilir
12573
+ (ayırıcı, tarayıcı dock'u, pencere boyutu, mod geçişi) */
12574
+ if (window.ResizeObserver) {
12575
+ const host = document.getElementById('sbPreviewHost');
12576
+ if (host) new ResizeObserver(() => sbPreviewLayout()).observe(host);
12577
+ }
12578
+
12579
+ /* ---------- sohbet/çalıştır ayırıcı (Beast Code ideSplit ile aynı desen) ----------
12580
+ Orantılı model: kaydedilen değer sohbet panelinin sandboxRow içindeki PAYI (0-1);
12581
+ sürükle-bırak anında --sbSplit yazar, bırakınca localStorage'a kaydeder. */
12582
+ let sbSplitFrac = 0;
12583
+
12584
+ function sbSplitApplyFrac() {
12585
+ const rowW = els.sandboxRow && els.sandboxRow.clientWidth ? els.sandboxRow.clientWidth : window.innerWidth - 250;
12586
+ /* kayıt yoksa eski varsayılan görünüm korunur: sohbet 480px, çalıştır kalanı */
12587
+ if (!sbSplitFrac) {
12588
+ const w = Math.max(280, Math.min(rowW - 480, Math.max(320, rowW - 300)));
12589
+ document.body.style.setProperty('--sbSplit', Math.round(w) + 'px');
12590
+ return;
12591
+ }
12592
+ sbSplitFrac = Math.max(0.2, Math.min(sbSplitFrac, 0.8));
12593
+ const w = Math.max(280, Math.min(Math.round(rowW * sbSplitFrac), Math.max(320, rowW - 300)));
12594
+ document.body.style.setProperty('--sbSplit', w + 'px');
12595
+ }
12596
+
12597
+ function sbSplitSave() {
12598
+ try { localStorage.setItem('beast.sbSplit', String(sbSplitFrac)); } catch {}
12599
+ }
12600
+
12601
+ function sbSplitRestore() {
12602
+ let saved = 0;
12603
+ try { saved = parseFloat(localStorage.getItem('beast.sbSplit')) || 0; } catch {}
12604
+ sbSplitFrac = saved > 0.05 && saved < 0.95 ? saved : 0;
12605
+ sbSplitApplyFrac();
12606
+ }
12607
+
12608
+ if (els.sbSplit) {
12609
+ els.sbSplit.addEventListener('mousedown', (e) => {
12610
+ e.preventDefault();
12611
+ els.sbSplit.classList.add('dragging');
12612
+ /* native önizleme view'i fare olaylarını yutabilir — sürükleme boyunca gizle,
12613
+ bırakınca geri getir (IDE'deki ayırıcı da aynı nedenle DOM üstünde çizilir) */
12614
+ sbPreviewSuspend = true;
12615
+ if (sbPreviewShown) beast.sbPreviewSet({ visible: false }).catch(() => {});
12616
+ const rightEdge = els.sbRun ? els.sbRun.getBoundingClientRect().right : window.innerWidth;
12617
+ const rowW = Math.max(600, els.sandboxRow && els.sandboxRow.clientWidth ? els.sandboxRow.clientWidth : window.innerWidth - 250);
12618
+ const move = (ev) => {
12619
+ const w = Math.max(280, Math.min(rightEdge - ev.clientX, rowW - 300));
12620
+ sbSplitFrac = Math.max(0.2, Math.min(w / rowW, 0.8));
12621
+ document.body.style.setProperty('--sbSplit', Math.round(w) + 'px');
12622
+ sbPreviewLayout();
12623
+ };
12624
+ const up = () => {
12625
+ document.removeEventListener('mousemove', move);
12626
+ document.removeEventListener('mouseup', up);
12627
+ els.sbSplit.classList.remove('dragging');
12628
+ sbSplitSave();
12629
+ sbPreviewSuspend = false;
12630
+ sbPreviewApply();
12631
+ };
12632
+ document.addEventListener('mousemove', move);
12633
+ document.addEventListener('mouseup', up);
12634
+ });
12635
+ window.addEventListener('resize', () => {
12636
+ if (sbModeOn()) {
12637
+ sbSplitApplyFrac();
12638
+ sbPreviewLayout();
12639
+ }
12640
+ });
12641
+ }
@@ -4271,7 +4271,7 @@ body.sandbox-mode #sandboxRow { display: flex; flex-direction: row; }
4271
4271
  flex-direction: column;
4272
4272
  min-height: 0;
4273
4273
  min-width: 300px;
4274
- flex: 0 0 var(--sbSplit, 480px);
4274
+ flex: 1 1 auto;
4275
4275
  background: var(--bg);
4276
4276
  overflow: hidden;
4277
4277
  }
@@ -4280,10 +4280,20 @@ body.sandbox-mode #sandboxRow { display: flex; flex-direction: row; }
4280
4280
  flex-direction: column;
4281
4281
  min-height: 0;
4282
4282
  min-width: 240px;
4283
- flex: 1;
4283
+ flex: 0 0 var(--sbSplit, 480px);
4284
4284
  background: var(--bg);
4285
4285
  overflow: hidden;
4286
- border-left: 1px solid var(--border);
4286
+ }
4287
+ #sbSplit {
4288
+ flex: none;
4289
+ width: 6px;
4290
+ cursor: col-resize;
4291
+ background: var(--border);
4292
+ opacity: 0.55;
4293
+ transition: opacity 0.15s;
4294
+ }
4295
+ #sbSplit:hover {
4296
+ opacity: 1;
4287
4297
  }
4288
4298
 
4289
4299
  #sbHead {
@@ -4394,9 +4404,31 @@ body.sandbox-mode #sandboxRow { display: flex; flex-direction: row; }
4394
4404
  padding: 7px 9px;
4395
4405
  border-radius: 8px;
4396
4406
  font-family: inherit;
4407
+ overflow-y: hidden;
4408
+ max-height: 140px;
4409
+ line-height: 1.45;
4410
+ display: block;
4397
4411
  }
4398
4412
  #sbInput:focus { border-color: var(--accent); outline: none; }
4399
4413
  #sbInput.expand { overflow-y: auto; }
4414
+ #sbInputRow button {
4415
+ flex: none;
4416
+ background: var(--panel2);
4417
+ border: 1px solid var(--border);
4418
+ color: var(--text);
4419
+ border-radius: 7px;
4420
+ cursor: pointer;
4421
+ font-size: 11px;
4422
+ padding: 6px 9px;
4423
+ line-height: 1;
4424
+ margin-bottom: 1px;
4425
+ }
4426
+ #sbInputRow button:hover { border-color: var(--accent); }
4427
+ #sbSend { color: var(--accent); }
4428
+ #sbSend:hover { background: var(--accent-dim); }
4429
+ #sbStop { color: var(--err); }
4430
+ #sbStop[hidden] { display: none; }
4431
+ #sbStop:hover { border-color: var(--err); }
4400
4432
 
4401
4433
  /* ---------- ÇALIŞTIR bölmesi ---------- */
4402
4434
  #sbRunHead {
@@ -4430,6 +4462,33 @@ body.sandbox-mode #sandboxRow { display: flex; flex-direction: row; }
4430
4462
  text-overflow: ellipsis;
4431
4463
  white-space: nowrap;
4432
4464
  }
4465
+ #sbRunHead button {
4466
+ flex: none;
4467
+ background: var(--panel2);
4468
+ border: 1px solid var(--border);
4469
+ color: var(--muted);
4470
+ font-size: 11px;
4471
+ padding: 3px 9px;
4472
+ border-radius: 6px;
4473
+ cursor: pointer;
4474
+ }
4475
+ #sbRunHead button:hover { color: var(--text); border-color: var(--accent); }
4476
+ #sbRunHead button.on { color: var(--accent); border-color: var(--accent); }
4477
+ #sbPreviewHost {
4478
+ flex: 1;
4479
+ min-height: 0;
4480
+ display: flex;
4481
+ align-items: center;
4482
+ justify-content: center;
4483
+ background: var(--bg);
4484
+ }
4485
+ #sbPreviewHost[hidden] { display: none; }
4486
+ #sbPreviewHint {
4487
+ color: var(--muted);
4488
+ font-size: 12px;
4489
+ padding: 0 16px;
4490
+ text-align: center;
4491
+ }
4433
4492
  #sbRunBtns {
4434
4493
  flex: none;
4435
4494
  display: flex;