beast-agent 1.4.1 → 1.5.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/src/main.js CHANGED
@@ -863,10 +863,15 @@ function stopEverything() {
863
863
  }
864
864
 
865
865
  function resumeServices() {
866
- if (!servicesPaused) return;
866
+ if (!servicesPaused) {
867
+ /* /stop kapısı burada da kalkar — /start VE kullanıcının kendi mesajı canlandırır */
868
+ try { engine.clearStop(); } catch {}
869
+ return;
870
+ }
867
871
  try { cron.init({ onFire: cronFire }); } catch {}
868
872
  try { watchers.start({ onTrigger: watcherFire }); } catch {}
869
873
  try { startEventBus(); } catch {}
874
+ try { engine.clearStop(); } catch {}
870
875
  servicesPaused = false;
871
876
  }
872
877
 
@@ -1100,7 +1105,7 @@ async function tryWaSlash(jid, rawText, senderNum, payload0) {
1100
1105
  const stopped = stopEverything();
1101
1106
  out =
1102
1107
  `*Durdu* — ${stopped} koşan iş kesildi.\n` +
1103
- `Paralel ajanlar ve bekleyen kuyruklar temizlendi. Cron, izleyici ve olay merkezi çalışmaya devam ediyor.\n` +
1108
+ `Sürüyen sorgular, akıştaki cevaplar ve ajan faaliyetleri ANINDA kesildi; ajan yeni sorgu da açamaz.\n` +
1104
1109
  `Devam için bir şeyler yaz ya da /start`;
1105
1110
  } else if (cmd === 'start') {
1106
1111
  if (servicesPaused) {
@@ -2009,7 +2014,30 @@ async function processWaMessage(jid, payload, senderNum, requeues = 0) {
2009
2014
  }
2010
2015
 
2011
2016
  if (payload.text) text += `\n${String(payload.text).slice(0, 6000)}`;
2012
- engine.send(sid, { text: text.slice(0, 8000), attachments });
2017
+ /* BEASTCODE MODU: WA'dan gelen kullanıcı mesajını masaüstü panelde de göster.
2018
+ Ajan cevabı engine olaylarıyla zaten panele akar ama KULLANICI mesajı için
2019
+ engine olay üretmez (panel kendi input'undan "code>" satırı basar) — bu
2020
+ yüzden açıkça bildiriyoruz; yoksa WhatsApp'tan yazılanlar panelde görünmez. */
2021
+ if (waBcMode.has(jid) && win && !win.isDestroyed()) {
2022
+ const waEcho = [];
2023
+ if (payload.text) waEcho.push(String(payload.text).slice(0, 6000));
2024
+ if (attachments.length) {
2025
+ waEcho.push(
2026
+ '[' +
2027
+ attachments
2028
+ .map((a) => (a.type === 'image' ? 'resim' : 'dosya: ' + (a.name || '?')))
2029
+ .join(', ') +
2030
+ ']'
2031
+ );
2032
+ }
2033
+ win.webContents.send('agent:event', {
2034
+ sessionId: sid,
2035
+ type: 'wa-user',
2036
+ from: isGroup ? (groupSender || 'grup üyesi') : (hit && hit.name) || '+' + (senderNum || '?'),
2037
+ text: waEcho.join('\n'),
2038
+ });
2039
+ }
2040
+ engine.send(sid, { text: text.slice(0, 8000), attachments }, { userAction: true });
2013
2041
  }
2014
2042
 
2015
2043
  function ensureWa() {
@@ -2218,7 +2246,7 @@ async function processTgMessage(chatId, payload, requeues = 0) {
2218
2246
  text += `\n[NOT: Bu kişi SAHİP DEĞİL, misafirdir. Sahibin ayarlarını/verilerini değiştirme; kalıcı hafızaya misafire özel bilgi yazma.]`;
2219
2247
  }
2220
2248
  text += `\n${String(payload.text || '').slice(0, 6000)}`;
2221
- engine.send(sid, { text: text.slice(0, 8000), attachments: [] });
2249
+ engine.send(sid, { text: text.slice(0, 8000), attachments: [] }, { userAction: true });
2222
2250
  }
2223
2251
 
2224
2252
  async function sendTgSafe(chatId, text) {
@@ -2436,7 +2464,7 @@ async function processDcMessage(channelId, payload) {
2436
2464
  text += `\n[NOT: Bu kişi SAHİP DEĞİL, misafirdir. Sahibin ayarlarını/verilerini değiştirme; kalıcı hafızaya misafire özel bilgi yazma.]`;
2437
2465
  }
2438
2466
  text += `\n${String(payload.text || '').slice(0, 6000)}`;
2439
- engine.send(sid, { text: text.slice(0, 8000), attachments: [] });
2467
+ engine.send(sid, { text: text.slice(0, 8000), attachments: [] }, { userAction: true });
2440
2468
  }
2441
2469
 
2442
2470
  async function sendDcSafe(channelId, text) {
@@ -2549,6 +2577,11 @@ function reloadBackend() {
2549
2577
  if (win && !win.isDestroyed()) win.webContents.send('agent:event', ev);
2550
2578
  flushDesktopOnDone(ev); /* biriken desktop mesajlarını sıraya bas */
2551
2579
  bcFlushOnDone(ev); /* Beast Code kuyruğunu iş bitiminde boşalt */
2580
+ /* BC canlı önizleme: ajan bir dev server başlattıysa adresi yakala —
2581
+ preview butonu ve otomatik açılış DAİMA bu sunucuyu öncelikli kullanır */
2582
+ if (ev.type === 'bc-preview' && /^https?:\/\/(?:localhost|127\.0\.0\.1)(?::\d{2,5})?/i.test(String(ev.url || ''))) {
2583
+ bcLastServerUrl = String(ev.url);
2584
+ }
2552
2585
  /* WA canlı iş takibi: oturum bir WhatsApp sohbetine bağlıysa araç
2553
2586
  hareketlerini (terminal, web, dosya…) kısa satırla bildir.
2554
2587
  Spam olmasın: sohbet başına en az 7 sn'de bir tek satır. */
@@ -2670,6 +2703,32 @@ function reloadBackend() {
2670
2703
  })();
2671
2704
  }
2672
2705
  }
2706
+ /* CRON → TÜM AKTİF ENTEGRASYONLAR: cron işi bittiğinde cevap (ya da
2707
+ hata) sahibin bağlı olduğu her kanala yansıtılır — WA + Telegram +
2708
+ Discord. Aynı kanal hem cron oturumuna bağlıysa TEK cevap alır. */
2709
+ if ((ev.type === 'done' || ev.type === 'error') && cronAnswerPending.has(String(ev.sessionId))) {
2710
+ const cjob = cronAnswerPending.get(String(ev.sessionId));
2711
+ cronAnswerPending.delete(String(ev.sessionId));
2712
+ if (!ev.aborted) {
2713
+ (async () => {
2714
+ try {
2715
+ let txt = '';
2716
+ if (ev.type === 'error') {
2717
+ txt = '⚠️ [cron: ' + String((cjob && cjob.name) || 'görev') + ']\nHata: ' + String(ev.error || '').slice(0, 200);
2718
+ } else {
2719
+ const s = engine.openSession(ev.sessionId);
2720
+ const lastA = [...s.messages].reverse().find((m) => m.role === 'assistant' && m.content);
2721
+ txt = typeof (lastA && lastA.content) === 'string' ? lastA.content : '';
2722
+ if (txt.trim()) txt = '⏰ [cron: ' + String((cjob && cjob.name) || 'görev') + ']\n' + txt;
2723
+ }
2724
+ if (!txt.trim()) return;
2725
+ for (const m of cronMirrorTargets(String(ev.sessionId))) {
2726
+ try { await m.send(txt); } catch {}
2727
+ }
2728
+ } catch {}
2729
+ })();
2730
+ }
2731
+ }
2673
2732
  },
2674
2733
  });
2675
2734
  return engine.publicState();
@@ -2832,6 +2891,7 @@ app.whenReady().then(() => {
2832
2891
  cron.init({ onFire: cronFire });
2833
2892
  watchers.start({ onTrigger: watcherFire });
2834
2893
  startEventBus();
2894
+ ideWatchStart(); // soldaki dosya ağacı canlı izlemede
2835
2895
  maybeRunWhereWasI();
2836
2896
  falloutResume();
2837
2897
  startAutoUpdater(); // #3 sessiz güncelleme
@@ -2915,18 +2975,57 @@ function showWin() {
2915
2975
 
2916
2976
  const BROWSER_TOOLBAR_H = 46;
2917
2977
  const BROWSER_START_URL = 'https://www.google.com/';
2978
+ /* TELEFON MODU: mobil UA + dar dock → sitelerin mobil versiyonu canlı izlenir;
2979
+ Expo/Metro dev sunucularında otomatik devreye girer */
2980
+ const PHONE_UA =
2981
+ 'Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/' +
2982
+ process.versions.chrome +
2983
+ ' Mobile Safari/537.36';
2918
2984
  /* Tarayıcı state: visible=false → ajanlar GİZLİ kullanır (headless);
2919
2985
  göz ikonuyla görünür mod açılır. open=view aktif, visible=panelde görünürlük */
2920
- const browser = { view: null, open: false, visible: false, width: 0, attached: false, started: false };
2986
+ const browser = { view: null, open: false, visible: false, width: 0, attached: false, started: false, phone: false, desktopUA: '' };
2921
2987
 
2922
2988
  function browserEmit(payload) {
2923
- if (win && !win.isDestroyed()) win.webContents.send('agent:event', { type: 'browser', visible: browser.visible, ...payload });
2989
+ if (win && !win.isDestroyed()) win.webContents.send('agent:event', { type: 'browser', visible: browser.visible, phone: browser.phone, ...payload });
2924
2990
  }
2925
2991
 
2926
2992
  function browserWidthFor(w) {
2927
2993
  return Math.max(380, Math.min(800, Math.floor(w * 0.46)));
2928
2994
  }
2929
2995
 
2996
+ function browserShownWidth(w) {
2997
+ /* telefon modunda dock genişliği KORUNUR — 390x844 telefon ekranı
2998
+ görüntünün ORTASINDA, cihaz çerçevesiyle gösterilir (DevTools gibi) */
2999
+ return Math.min(browser.width, Math.max(320, w - 320));
3000
+ }
3001
+
3002
+ /* telefon modu görüntü parametreleri: mobil ekran emülasyonu + sığma ölçeği */
3003
+ function applyPhoneEmulation() {
3004
+ try {
3005
+ const wc = browser.view && browser.view.webContents;
3006
+ if (!wc) return;
3007
+ if (!browser.phone) {
3008
+ wc.disableDeviceEmulation();
3009
+ return;
3010
+ }
3011
+ let w = 800;
3012
+ let h = 900;
3013
+ try { [w, h] = win.getContentSize(); } catch {}
3014
+ const viewW = browserShownWidth(w);
3015
+ const viewH = Math.max(240, h - BROWSER_TOOLBAR_H - 8);
3016
+ const PW = 390;
3017
+ const PH = 844;
3018
+ const scale = Math.max(0.3, Math.min(1, (viewW - 20) / PW, (viewH - 12) / PH));
3019
+ wc.enableDeviceEmulation({
3020
+ screenPosition: 'mobile',
3021
+ screenOrientation: { type: 'portraitPrimary', angle: 0 },
3022
+ viewSize: { width: PW, height: PH },
3023
+ deviceScaleFactor: 2,
3024
+ scale,
3025
+ });
3026
+ } catch {}
3027
+ }
3028
+
2930
3029
  function layoutBrowser() {
2931
3030
  if (!win || win.isDestroyed()) return;
2932
3031
  const [w, h] = win.getContentSize();
@@ -2937,7 +3036,7 @@ function layoutBrowser() {
2937
3036
  const saved = Math.round(Number(settings.browserWidth) || 0);
2938
3037
  browser.width = saved >= 300 ? saved : browserWidthFor(w);
2939
3038
  }
2940
- const shownWidth = Math.min(browser.width, Math.max(320, w - 320));
3039
+ const shownWidth = browserShownWidth(w);
2941
3040
  if (!browser.view || !browser.open) return;
2942
3041
  const view = browser.view;
2943
3042
  if (!browser.attached) {
@@ -2949,6 +3048,7 @@ function layoutBrowser() {
2949
3048
  try {
2950
3049
  view.setBounds({ x: Math.max(0, w - shownWidth), y: BROWSER_TOOLBAR_H, width: shownWidth, height: Math.max(0, h - BROWSER_TOOLBAR_H) });
2951
3050
  view.setVisible(browser.open && browser.visible);
3051
+ if (browser.phone) applyPhoneEmulation(); // pencere boyutlanınca telefon ölçeği tazelensin
2952
3052
  } catch {}
2953
3053
  }
2954
3054
 
@@ -2975,6 +3075,8 @@ function ensureBrowser() {
2975
3075
  const bses = session.fromPartition('persist:browser');
2976
3076
  if (bses && bses.setUserAgent) bses.setUserAgent(chromeUA, 'tr-TR,tr;q=0.9,en;q=0.8');
2977
3077
  wc.setUserAgent(chromeUA);
3078
+ browser.desktopUA = chromeUA;
3079
+ if (browser.phone) wc.setUserAgent(PHONE_UA); // telefon modu açıkken mobil UA ile doğ
2978
3080
  } catch {}
2979
3081
  wc.setWindowOpenHandler(({ url }) => {
2980
3082
  if (/^https?:\/\//i.test(url)) wc.loadURL(url).catch(() => {});
@@ -2999,6 +3101,7 @@ function ensureBrowser() {
2999
3101
  browser.view = null;
3000
3102
  });
3001
3103
  browser.view = view;
3104
+ if (browser.phone) applyPhoneEmulation(); // tarayıcı telefon modunda doğarsa emülasyonu uygula
3002
3105
  return view;
3003
3106
  }
3004
3107
 
@@ -3900,6 +4003,8 @@ function createWindow() {
3900
4003
  /* ---------------- IPC ---------------- */
3901
4004
 
3902
4005
  ipcMain.handle('app:state', () => engine.publicState());
4006
+ /* Ayarlar penceresi altında küçük sürüm etiketi (ör. v1.4.2) */
4007
+ ipcMain.handle('app:version', () => beastVersion());
3903
4008
 
3904
4009
  /* #3 log sistemi: ayarlar → Log sekmesinde görüntülenir */
3905
4010
  ipcMain.handle('logs:get', () => {
@@ -4272,7 +4377,7 @@ function handleGlobalStopStart(sessionId, cmd) {
4272
4377
  const n = stopEverything();
4273
4378
  reply =
4274
4379
  `\u25A0 **Durdu** — ${n} koşan iş kesildi.\n` +
4275
- 'Paralel ajanlar ve bekleyen kuyruklar temizlendi. Cron, izleyici ve olay merkezi çalışmaya devam ediyor.\n' +
4380
+ 'Sürüyen sorgular, akıştaki cevaplar ve ajan faaliyetleri ANINDA kesildi; ajan yeni sorgu da açamaz.\n' +
4276
4381
  'Devam için bir şeyler yaz ya da `/start`';
4277
4382
  } else {
4278
4383
  const wasPaused = servicesPaused;
@@ -4369,7 +4474,7 @@ async function flushDesktop(sessionId) {
4369
4474
  if (!mergedAtts && Array.isArray(m.attachments)) mergedAtts = m.attachments;
4370
4475
  }
4371
4476
  if (!mergedText.trim() && !mergedAtts) return;
4372
- engine.send(sid, mergedAtts ? { text: mergedText, attachments: mergedAtts } : mergedText);
4477
+ engine.send(sid, mergedAtts ? { text: mergedText, attachments: mergedAtts } : mergedText, { userAction: true });
4373
4478
  }
4374
4479
 
4375
4480
  /* agent işi bitince biriken masaüstü mesajlarını göndere bastır.
@@ -4604,13 +4709,13 @@ ipcMain.handle('model:set', (_e, sel) => {
4604
4709
 
4605
4710
  /* Paralel ajan geçmişini TOPLUCA sil (rail başlığındaki çöp ikonu) */
4606
4711
  ipcMain.handle('agents:clearAll', () => {
4607
- try {
4608
- const removed = engine.clearAllBgJobs();
4609
- return { ok: true, removed };
4610
- } catch (e) {
4611
- return { ok: false, error: String((e && e.message) || e) };
4612
- }
4613
- });
4712
+ try {
4713
+ const removed = engine.clearAllBgJobs();
4714
+ return { ok: true, removed };
4715
+ } catch (e) {
4716
+ return { ok: false, error: String((e && e.message) || e) };
4717
+ }
4718
+ });
4614
4719
 
4615
4720
  ipcMain.handle('model:role', (_e, map) => {
4616
4721
  const roleModels = {};
@@ -5098,6 +5203,57 @@ function cronEmit() {
5098
5203
  }
5099
5204
  }
5100
5205
 
5206
+ /* Cron cevabı bekleme haritası: sid -> job. İş 'done' olunca cevap
5207
+ SAHİBİN bağlı olduğu TÜM AKTİF entegrasyonlara yansıtılır (kullanıcı
5208
+ hangi kanaldan ajanla iletişimde belli değil). */
5209
+ const cronAnswerPending = new Map();
5210
+
5211
+ /* Yansıtma hedefleri: bağlı WA (owner numarası), Telegram ve Discord
5212
+ (owner işaretli kayıt; tek kayıt varsa o). Cron oturumunun KENDİ
5213
+ kanalına yansıtmayız — cevabı zaten kendi akışından alır (çift yok). */
5214
+ function tgOwnerIds() {
5215
+ const list = settings.tgAllow || [];
5216
+ const objs = list.filter((e) => e && typeof e === 'object' && e.id && e.id !== '*');
5217
+ const owner = objs.find((e) => e.owner) || (objs.length === 1 ? objs[0] : null);
5218
+ return owner ? [String(owner.id)] : [];
5219
+ }
5220
+ function dcOwnerIds() {
5221
+ const list = settings.dcAllow || [];
5222
+ const objs = list.filter((e) => e && typeof e === 'object' && e.id && e.id !== '*');
5223
+ const owner = objs.find((e) => e.owner) || (objs.length === 1 ? objs[0] : null);
5224
+ return owner ? [String(owner.id)] : [];
5225
+ }
5226
+ function cronMirrorTargets(cronSid) {
5227
+ const out = [];
5228
+ try {
5229
+ if (wa && wa.connected) {
5230
+ const own = waOwnerNum();
5231
+ if (own) {
5232
+ const jid = own + '@s.whatsapp.net';
5233
+ const bound = [...waChats.entries()].some(([j, s]) => j === jid && String(s) === String(cronSid));
5234
+ if (!bound) out.push({ kind: 'wa', send: (t) => sendWaSafe(jid, t) });
5235
+ }
5236
+ }
5237
+ } catch {}
5238
+ try {
5239
+ if (tg && tg.connected) {
5240
+ for (const id of tgOwnerIds()) {
5241
+ const bound = [...tgChats.entries()].some(([c, s]) => String(c) === String(id) && String(s) === String(cronSid));
5242
+ if (!bound) out.push({ kind: 'tg', send: (t) => sendTgSafe(id, t) });
5243
+ }
5244
+ }
5245
+ } catch {}
5246
+ try {
5247
+ if (dc && dc.connected) {
5248
+ for (const id of dcOwnerIds()) {
5249
+ const bound = [...dcChats.entries()].some(([c, s]) => String(c) === String(id) && String(s) === String(cronSid));
5250
+ if (!bound) out.push({ kind: 'dc', send: (t) => sendDcSafe(id, t) });
5251
+ }
5252
+ }
5253
+ } catch {}
5254
+ return out;
5255
+ }
5256
+
5101
5257
  function cronFire(job) {
5102
5258
  try {
5103
5259
  let sid = job.sessionId;
@@ -5107,9 +5263,11 @@ function cronFire(job) {
5107
5263
  sid = s.id;
5108
5264
  cron.update(job.id, { sessionId: sid });
5109
5265
  }
5110
- engine.send(sid, {
5266
+ cronAnswerPending.set(String(sid), job);
5267
+ const sent = engine.send(sid, {
5111
5268
  text: `[cron: ${job.name}]\n${job.prompt}`,
5112
5269
  });
5270
+ if (!sent) cronAnswerPending.delete(String(sid)); // gönderilemedi — bayat bekleme bırakma
5113
5271
  } catch {}
5114
5272
  cronEmit();
5115
5273
  }
@@ -5251,9 +5409,35 @@ ipcMain.handle('browser:setWidth', (_e, wpx) => {
5251
5409
  settings.browserWidth = browser.width;
5252
5410
  saveSettings();
5253
5411
  layoutBrowser();
5254
- browserEmit({ open: true, width: browser.width });
5412
+ browserEmit({ open: true, width: browserShownWidth(w) });
5255
5413
  return { ok: true, width: browser.width };
5256
5414
  });
5415
+
5416
+ /* TELEFON MODU: mobil UA + dar dock (≈430px) → aynı sitenin mobil versiyonu.
5417
+ Kapatınca masaüstü UA + kayıtlı genişlik geri gelir. UA değişimi için sayfa
5418
+ taze yüklenir. Expo/Metro dev sunucularında otomatik açılır. */
5419
+ function setBrowserPhone(on) {
5420
+ const want = !!on;
5421
+ if (browser.phone === want) return { ok: true, phone: want };
5422
+ browser.phone = want;
5423
+ try {
5424
+ const wc = browser.view && browser.view.webContents;
5425
+ if (wc) {
5426
+ wc.setUserAgent(browser.phone ? PHONE_UA : browser.desktopUA || PHONE_UA);
5427
+ let url = '';
5428
+ try { url = wc.getURL(); } catch {}
5429
+ if (url && /^https?:/i.test(url)) wc.loadURL(url).catch(() => {});
5430
+ }
5431
+ } catch {}
5432
+ applyPhoneEmulation(); // açık: 390x844 cihaz ortalanır — kapalı: emülasyon kapanır
5433
+ if (browser.open && win && !win.isDestroyed()) {
5434
+ layoutBrowser();
5435
+ const [w] = win.getContentSize();
5436
+ browserEmit({ open: true, width: browserShownWidth(w) });
5437
+ }
5438
+ return { ok: true, phone: browser.phone };
5439
+ }
5440
+ ipcMain.handle('browser:phone', (_e, on) => setBrowserPhone(on));
5257
5441
  ipcMain.handle('browser:screenshot', async () => {
5258
5442
  const r = await browserScreenshot();
5259
5443
  if (!r.ok) return { ok: false, error: r.error };
@@ -5264,31 +5448,95 @@ ipcMain.handle('browser:screenshot', async () => {
5264
5448
  Sağ dock paneli renderer'da; main sadece PowerShell komutlarını
5265
5449
  çalıştırır ve çıktıyı canlı akıtır. Terminal ile tarayıcı aynı
5266
5450
  dock'u paylaştığı için ikisi aynı anda açık kalamaz. */
5267
- let termChild = null;
5451
+ let termChild = null; /* KALICI CMD oturumu — cd/set değişkenleri komutlar arasında KORUNUR */
5452
+ let termShellCwd = ''; /* izlenen çalışma klasörü (komut sonundaki marker'dan) */
5453
+ let termShellId = null;
5454
+ let termShellSeq = 0;
5455
+ let termForwarded = 0;
5456
+ let termCapNotified = false;
5268
5457
  const TERM_FWD_CAP = 1024 * 1024; /* iletilecek çıktı üst sınırı (1 MB) */
5458
+ const TERM_MARKER = '__BEAST_EOF__';
5269
5459
 
5270
5460
  function termSend(ev) {
5271
5461
  if (win && !win.isDestroyed()) win.webContents.send('agent:event', ev);
5272
5462
  }
5273
5463
 
5464
+ /* Kalıcı kabuğu başlat: cmd /q /k — girdi pipe'ından satır satır okur,
5465
+ prompt yazmaz. chcp 65001 → Türkçe yollar (Masaüstü vb.) doğru çözülür. */
5466
+ function termShellSpawn() {
5467
+ const cwd =
5468
+ termShellCwd ||
5469
+ (engine && engine.workspace) ||
5470
+ settings.workspace ||
5471
+ app.getPath('home');
5472
+ const child = spawn('cmd.exe', ['/q', '/k'], {
5473
+ cwd,
5474
+ windowsHide: true,
5475
+ stdio: ['pipe', 'pipe', 'pipe'],
5476
+ });
5477
+ termShellCwd = cwd;
5478
+ termChild = child;
5479
+ termForwarded = 0;
5480
+ termCapNotified = false;
5481
+ child.stdout.setEncoding('utf8');
5482
+ child.stderr.setEncoding('utf8');
5483
+ child.stdout.on('data', (d) => {
5484
+ termForwarded += String(d).length;
5485
+ if (termForwarded > TERM_FWD_CAP) {
5486
+ if (!termCapNotified) {
5487
+ termCapNotified = true;
5488
+ termSend({ type: 'term-out', id: termShellId, stream: 'out', chunk: '\n[beast] çıktı çok büyük — iletim durduruldu (komut sürüyor)\n' });
5489
+ }
5490
+ return;
5491
+ }
5492
+ termSend({ type: 'term-out', id: termShellId, stream: 'out', chunk: String(d) });
5493
+ });
5494
+ child.stderr.on('data', (d) => {
5495
+ termSend({ type: 'term-out', id: termShellId, stream: 'err', chunk: String(d) });
5496
+ });
5497
+ child.on('exit', () => {
5498
+ if (termChild === child) termChild = null;
5499
+ });
5500
+ /* UTF-8 kod sayfası: Türkçe karakterli klasörler/çıktılar bozulmadan akar.
5501
+ (cmd her stdin satırını bir komut işler — bu ilk satır chcp olur) */
5502
+ try { child.stdin.write('chcp 65001 > nul\r\n'); } catch {}
5503
+ }
5504
+
5274
5505
  ipcMain.handle('terminal:toggle', () => {
5275
5506
  if (browser.open) setBrowserOpen(false);
5276
- return { ok: true, cwd: (engine && engine.workspace) || settings.workspace || app.getPath('home') };
5507
+ return { ok: true, cwd: termShellCwd || (engine && engine.workspace) || settings.workspace || app.getPath('home') };
5277
5508
  });
5278
5509
 
5279
5510
  ipcMain.handle('terminal:run', (_e, payload) => {
5280
5511
  const cmd = String((payload && payload.cmd) || '').trim();
5281
5512
  const shell = String((payload && payload.shell) || 'cmd');
5282
5513
  if (!cmd) return { ok: false, error: 'boş komut' };
5283
- if (termChild) return { ok: false, error: 'önceki komut sürüyor ile durdurabilirsin' };
5284
- const cwd = (engine && engine.workspace) || settings.workspace || app.getPath('home');
5514
+ /* kalıcı CMD: komut AYNI kabuğa yazılır cd/set kalıcıdır; komutlar sıraya girer */
5515
+ if (shell === 'cmd') {
5516
+ if (!termChild || !termChild.stdin.writable) termShellSpawn();
5517
+ if (!termChild) return { ok: false, error: 'kabuk başlatılamadı' };
5518
+ const id = 't' + Date.now().toString(36) + ++termShellSeq;
5519
+ termShellId = id;
5520
+ termForwarded = 0;
5521
+ termCapNotified = false;
5522
+ try {
5523
+ /* İKİ AYRI satır: cmd her satırı SIRAYLA işler — ikinci satırdaki %CD%
5524
+ ancak ilk komut BİTİNCE okunur/genişletilir → doğru (yeni) klasör gelir.
5525
+ Aynı satıra & echo yazsaydık %CD% eski klasörü verirdi. */
5526
+ termChild.stdin.write(cmd + '\r\n' + 'echo ' + TERM_MARKER + '%CD%' + TERM_MARKER + '\r\n');
5527
+ } catch (e) {
5528
+ return { ok: false, error: String((e && e.message) || e) };
5529
+ }
5530
+ return { ok: true, id };
5531
+ }
5532
+ /* geriye dönük uyumluluk — UI artık yalnız CMD gönderir */
5533
+ const cwd = termShellCwd || (engine && engine.workspace) || settings.workspace || app.getPath('home');
5285
5534
  const id = 't' + Date.now().toString(36);
5286
5535
  let file, args;
5287
5536
  if (shell === 'cmd') {
5288
5537
  file = 'cmd.exe';
5289
5538
  args = ['/d', '/s', '/c', cmd];
5290
5539
  } else {
5291
- /* geriye dönük uyumluluk — UI artık yalnız CMD gönderir */
5292
5540
  file = 'powershell.exe';
5293
5541
  args = ['-NoProfile', '-NoLogo', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', cmd];
5294
5542
  }
@@ -5298,38 +5546,29 @@ ipcMain.handle('terminal:run', (_e, payload) => {
5298
5546
  } catch (e) {
5299
5547
  return { ok: false, error: String((e && e.message) || e) };
5300
5548
  }
5301
- termChild = child;
5302
5549
  child.stdout.setEncoding('utf8');
5303
5550
  child.stderr.setEncoding('utf8');
5304
- let forwarded = 0;
5305
- let capNotified = false;
5306
- const push = (stream, d) => {
5307
- if (forwarded > TERM_FWD_CAP) {
5308
- if (!capNotified) {
5309
- capNotified = true;
5310
- termSend({ type: 'term-out', id, stream, chunk: '\n[beast] çıktı çok büyük — iletim durduruldu (komut sürüyor)\n' });
5311
- }
5312
- return;
5313
- }
5314
- forwarded += String(d).length;
5315
- termSend({ type: 'term-out', id, stream, chunk: String(d) });
5316
- };
5317
- child.stdout.on('data', (d) => push('out', d));
5318
- child.stderr.on('data', (d) => push('err', d));
5551
+ child.stdout.on('data', (d) => termSend({ type: 'term-out', id, stream: 'out', chunk: String(d) }));
5552
+ child.stderr.on('data', (d) => termSend({ type: 'term-out', id, stream: 'err', chunk: String(d) }));
5319
5553
  child.on('error', (err) => {
5320
- if (termChild === child) termChild = null;
5321
5554
  termSend({ type: 'term-end', id, code: -1, error: String((err && err.message) || err) });
5322
5555
  });
5323
5556
  child.on('close', (code) => {
5324
- if (termChild === child) termChild = null;
5325
5557
  termSend({ type: 'term-end', id, code: code == null ? -1 : code });
5326
5558
  });
5327
5559
  return { ok: true, id };
5328
5560
  });
5329
5561
 
5330
5562
  ipcMain.handle('terminal:stop', () => {
5331
- if (!termChild) return { ok: false };
5332
- try { spawn('taskkill', ['/pid', String(termChild.pid), '/T', '/F'], { windowsHide: true }); } catch {}
5563
+ /* ■: süren komutu (tüm alt süreçleriyle) kes; KALICI kabuk yenilenir
5564
+ son bilinen klasör korunur, cd geçmişi kaybolmaz */
5565
+ if (termChild) {
5566
+ try { spawn('taskkill', ['/pid', String(termChild.pid), '/T', '/F'], { windowsHide: true }); } catch {}
5567
+ try { termChild.kill(); } catch {}
5568
+ termChild = null;
5569
+ }
5570
+ termSend({ type: 'term-end', id: termShellId, code: 130, error: 'komut durduruldu — kalıcı CMD yeniden hazır' });
5571
+ termShellId = null;
5333
5572
  return { ok: true };
5334
5573
  });
5335
5574
 
@@ -5382,13 +5621,16 @@ function bcPanelEvent(sid, ev) {
5382
5621
  const BC_DEBOUNCE_MS = 900;
5383
5622
  const bcQueue = new Map(); /* klasör yolu → { timer, msgs[] } */
5384
5623
 
5385
- function bcQueuePush(ws, text) {
5624
+ function bcQueuePush(ws, text, attachments) {
5386
5625
  let q = bcQueue.get(ws);
5387
5626
  if (!q) {
5388
5627
  q = { timer: null, msgs: [] };
5389
5628
  bcQueue.set(ws, q);
5390
5629
  }
5391
- q.msgs.push({ text });
5630
+ q.msgs.push({
5631
+ text,
5632
+ attachments: Array.isArray(attachments) && attachments.length ? attachments : undefined,
5633
+ });
5392
5634
  return q;
5393
5635
  }
5394
5636
 
@@ -5398,8 +5640,12 @@ function bcFlush(folder) {
5398
5640
  const s = bcGetSession(folder); /* oturum yoksa oluşturur */
5399
5641
  if (engine.isBusy(s.id)) return; /* hâlâ çalışıyor — done/error eventini bekle */
5400
5642
  let merged = '';
5401
- for (const m of q.msgs) if (m.text) merged += (merged ? '\n' : '') + m.text;
5402
- if (!merged.trim()) {
5643
+ let mergedAtts = null;
5644
+ for (const m of q.msgs) {
5645
+ if (m.text) merged += (merged ? '\n' : '') + m.text;
5646
+ if (!mergedAtts && Array.isArray(m.attachments) && m.attachments.length) mergedAtts = m.attachments;
5647
+ }
5648
+ if (!merged.trim() && !mergedAtts) {
5403
5649
  bcQueue.delete(folder);
5404
5650
  clearTimeout(q.timer);
5405
5651
  return;
@@ -5407,7 +5653,8 @@ function bcFlush(folder) {
5407
5653
  s.workspace = folder;
5408
5654
  s.bcCode = true;
5409
5655
  engine.cache.set(s.id, s);
5410
- if (engine.send(s.id, merged)) {
5656
+ const payload = mergedAtts ? { text: merged, attachments: mergedAtts } : merged;
5657
+ if (engine.send(s.id, payload, { userAction: true })) {
5411
5658
  bcQueue.delete(folder);
5412
5659
  clearTimeout(q.timer);
5413
5660
  } else {
@@ -5438,7 +5685,8 @@ function bcFlushOnDone(ev) {
5438
5685
 
5439
5686
  ipcMain.handle('beastcode:send', async (_e, payload) => {
5440
5687
  const text = String((payload && payload.msg) || '').trim();
5441
- if (!text) return { ok: false, error: 'boş mesaj' };
5688
+ const attachments = Array.isArray(payload && payload.attachments) ? payload.attachments : [];
5689
+ if (!text && !attachments.length) return { ok: false, error: 'boş mesaj' };
5442
5690
  if (!engine) return { ok: false, error: 'ajan hazır değil' };
5443
5691
  const ws = ideRoot();
5444
5692
  const modeM = /^\/(plan|build|auto)\b/i.exec(text);
@@ -5466,18 +5714,36 @@ ipcMain.handle('beastcode:send', async (_e, payload) => {
5466
5714
  }
5467
5715
  if (busy) {
5468
5716
  /* ajan çalışıyor → kuyruğa al; iş bitince (done/error) toplu gider */
5469
- const q = bcQueuePush(ws, text);
5717
+ const q = bcQueuePush(ws, text, attachments);
5470
5718
  return { ok: true, queued: true, count: q.msgs.length, sessionId: s.id };
5471
5719
  }
5472
5720
  /* boşta: kısa pencere — hızlı ard arda mesajlar tek işte birleşir */
5473
- const q = bcQueuePush(ws, text);
5721
+ const q = bcQueuePush(ws, text, attachments);
5474
5722
  clearTimeout(q.timer);
5475
5723
  q.timer = setTimeout(() => { try { bcFlush(ws); } catch {} }, BC_DEBOUNCE_MS);
5476
5724
  return { ok: true, sessionId: s.id, pending: true };
5477
5725
  });
5478
5726
 
5479
- ipcMain.handle('beastcode:stop', async () => {
5480
- const ws = ideRoot();
5727
+ /* BC görev listesi (ID'li) + TEK TUŞ GERİ ALMA: bir görev maddesinin
5728
+ değişiklikleri, madde başlamadan önceki kod tabanına döndürülür */
5729
+ ipcMain.handle('bc:todos', (_e, payload) => {
5730
+ try {
5731
+ return engine.todoUndoInfo(String((payload && payload.sessionId) || ''));
5732
+ } catch (e) {
5733
+ return { ok: false, error: String((e && e.message) || e) };
5734
+ }
5735
+ });
5736
+ ipcMain.handle('bc:undo', (_e, payload) => {
5737
+ try {
5738
+ const sid = String((payload && payload.sessionId) || '');
5739
+ const todoId = String((payload && payload.todoId) || '');
5740
+ return todoId === 'last' ? engine.undoLastTodo(sid) : engine.undoTodo(sid, todoId);
5741
+ } catch (e) {
5742
+ return { ok: false, error: String((e && e.message) || e) };
5743
+ }
5744
+ });
5745
+
5746
+ ipcMain.handle('beastcode:stop', async () => { const ws = ideRoot();
5481
5747
  const sid = bcSessions.get(ws);
5482
5748
  /* durdurma: bekleyen kuyruğu da boşalt (kullanıcı vazgeçti) */
5483
5749
  const q = bcQueue.get(ws);
@@ -5504,6 +5770,7 @@ ipcMain.handle('beastcode:new', async () => {
5504
5770
  }
5505
5771
  if (sid) {
5506
5772
  try { engine.deleteSession(sid); } catch {}
5773
+ bcLastServerUrl = ''; /* yeni oturum — eski dev server adresi geçersiz */
5507
5774
  bcSessions.delete(ws);
5508
5775
  }
5509
5776
  return { ok: true };
@@ -5704,6 +5971,7 @@ ipcMain.handle('ide:setroot', async () => {
5704
5971
  if (r.canceled || !r.filePaths || !r.filePaths[0]) return { ok: false, canceled: true };
5705
5972
  settings.ideRoot = r.filePaths[0];
5706
5973
  saveSettings();
5974
+ ideWatchStart(); // yeni kökte izleme yeniden kurulur
5707
5975
  return { ok: true, root: settings.ideRoot };
5708
5976
  } catch (e) {
5709
5977
  return { ok: false, error: String((e && e.message) || e) };
@@ -5718,6 +5986,56 @@ function ideSafe(rel) {
5718
5986
  return p;
5719
5987
  }
5720
5988
 
5989
+ /* ---------- IDE DOSYA AĞACI CANLI İZLEME ----------
5990
+ Soldaki klasör paneli ELLE yenilemeden güncellensin: workspace kökü
5991
+ fs.watch (recursive) ile izlenir; node_modules/.git gürültüsü elenir,
5992
+ 500ms debounce ile renderer'a 'ide-tree-changed' düşer. Ajan dışında
5993
+ (kullanıcı kaydı, git, harici program) değişen dosyalar da yakalanır. */
5994
+ let ideWatcher = null;
5995
+ let ideWatchTimer = null;
5996
+ let ideWatchRoot = '';
5997
+ function ideWatchStart() {
5998
+ const root = ideRoot();
5999
+ if (ideWatcher && ideWatchRoot === root) return;
6000
+ ideWatchStop();
6001
+ /* ev dizininin KÖKÜNÜ izlemek AppData gürültüsü yüzünden paneli sürekli
6002
+ yeniler — özel klasör seçiliyken (ideRoot/workspace) izleme aktiftir */
6003
+ if (root === app.getPath('home')) return;
6004
+ try {
6005
+ fs.accessSync(root); // kök yoksa izleme kurma
6006
+ } catch {
6007
+ return;
6008
+ }
6009
+ ideWatchRoot = root;
6010
+ try {
6011
+ ideWatcher = fs.watch(root, { recursive: true }, (_evType, fname) => {
6012
+ const f = String(fname || '').replace(/\\/g, '/');
6013
+ if (/^(node_modules|\.git|dist|\.next|\.nuxt)(\/|$)/i.test(f)) return;
6014
+ if (ideWatchTimer) return;
6015
+ ideWatchTimer = setTimeout(() => {
6016
+ ideWatchTimer = null;
6017
+ try {
6018
+ if (win && !win.isDestroyed()) {
6019
+ win.webContents.send('agent:event', { type: 'ide-tree-changed' });
6020
+ }
6021
+ } catch {}
6022
+ }, 500);
6023
+ });
6024
+ ideWatcher.on('error', () => {
6025
+ ideWatchStop();
6026
+ /* kök klasör silinip yeniden yaratıldıysa kısa süre sonra tekrar dene */
6027
+ setTimeout(() => { try { ideWatchStart(); } catch {} }, 3000);
6028
+ });
6029
+ try { log.info('ide', 'ağaç izleme açık: ' + root); } catch {}
6030
+ } catch {}
6031
+ }
6032
+ function ideWatchStop() {
6033
+ if (ideWatcher) { try { ideWatcher.close(); } catch {} }
6034
+ ideWatcher = null;
6035
+ ideWatchRoot = '';
6036
+ if (ideWatchTimer) { clearTimeout(ideWatchTimer); ideWatchTimer = null; }
6037
+ }
6038
+
5721
6039
  ipcMain.handle('ide:tree', (_e, rel) => {
5722
6040
  const p = ideSafe(rel);
5723
6041
  if (!p) return { ok: false, error: 'geçersiz yol' };
@@ -5795,25 +6113,102 @@ ipcMain.handle('ide:delete', async (_e, rel) => {
5795
6113
  });
5796
6114
 
5797
6115
  /* Sağ tık menüsü: HTML dosyasını dahili tarayıcıda GÖRÜNÜR aç */
5798
- ipcMain.handle('ide:previewFile', (_e, rel) => {
6116
+ /* ---------- BC DAHİLİ STATİK SUNUCU ----------
6117
+ Beast Code çıktısı ASLA file:// ile açılmaz: ES modülleri, fetch, ServiceWorker
6118
+ ve "clean URL" yolları file://'da çalışmaz. Her preview http://127.0.0.1 üzerinden
6119
+ servis edilir; ajan kendi dev sunucusunu başlattıysa o adres önceliklidir. */
6120
+ let bcLastServerUrl = ''; /* ajanın başlattığı dev server adresi (bc-preview'dan yakalanır) */
6121
+ const bcStatic = { server: null, root: '', base: '' };
6122
+ const BC_MIME = {
6123
+ '.html': 'text/html; charset=utf-8', '.htm': 'text/html; charset=utf-8',
6124
+ '.js': 'text/javascript; charset=utf-8', '.mjs': 'text/javascript; charset=utf-8',
6125
+ '.css': 'text/css; charset=utf-8', '.json': 'application/json; charset=utf-8',
6126
+ '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif',
6127
+ '.svg': 'image/svg+xml', '.ico': 'image/x-icon', '.webp': 'image/webp', '.avif': 'image/avif',
6128
+ '.woff': 'font/woff', '.woff2': 'font/woff2', '.ttf': 'font/ttf', '.otf': 'font/otf',
6129
+ '.txt': 'text/plain; charset=utf-8', '.md': 'text/plain; charset=utf-8',
6130
+ '.map': 'application/json', '.wasm': 'application/wasm', '.pdf': 'application/pdf',
6131
+ '.mp3': 'audio/mpeg', '.wav': 'audio/wav', '.mp4': 'video/mp4', '.webm': 'video/webm',
6132
+ '.xml': 'application/xml', '.csv': 'text/csv',
6133
+ };
6134
+ function bcStaticHandler(req, res) {
6135
+ try {
6136
+ let rel = decodeURIComponent(String((req && req.url) || '/').split('?')[0]);
6137
+ rel = rel.replace(/^\/+/, '');
6138
+ const fp = path.resolve(bcStatic.root, rel);
6139
+ if (fp !== bcStatic.root && !fp.startsWith(bcStatic.root + path.sep)) {
6140
+ res.writeHead(403); res.end('forbidden'); return;
6141
+ }
6142
+ const isFile = (p) => { try { return fs.statSync(p).isFile() ? p : null; } catch { return null; } };
6143
+ let hit = isFile(fp);
6144
+ if (!hit) hit = isFile(path.join(fp, 'index.html')); /* dizin → index.html */
6145
+ if (!hit) hit = isFile(fp + '.html'); /* clean URL: /hakkinda → hakkinda.html */
6146
+ if (!hit) { res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' }); res.end('404 — /' + rel); return; }
6147
+ const ext = path.extname(hit).toLowerCase();
6148
+ res.writeHead(200, { 'Content-Type': BC_MIME[ext] || 'application/octet-stream', 'Cache-Control': 'no-store' });
6149
+ fs.createReadStream(hit).pipe(res);
6150
+ } catch (e) {
6151
+ try { res.writeHead(500); res.end('server error'); } catch {}
6152
+ }
6153
+ }
6154
+ function bcStaticStart(root) {
6155
+ return new Promise((resolve) => {
6156
+ const r = path.resolve(String(root || ''));
6157
+ if (bcStatic.server && bcStatic.root === r) { resolve(bcStatic.base); return; }
6158
+ if (bcStatic.server) { try { bcStatic.server.close(); } catch {} bcStatic.server = null; }
6159
+ const srv = http.createServer(bcStaticHandler);
6160
+ srv.on('error', () => resolve(''));
6161
+ srv.listen(0, '127.0.0.1', () => {
6162
+ bcStatic.server = srv;
6163
+ bcStatic.root = r;
6164
+ bcStatic.base = 'http://127.0.0.1:' + srv.address().port;
6165
+ try { log.info('bc', 'statik sunucu: ' + bcStatic.base + ' → ' + r); } catch {}
6166
+ resolve(bcStatic.base);
6167
+ });
6168
+ });
6169
+ }
6170
+ /* Preview'a basılınca BC oturumuna SESSİZ bağlam enjeksiyonu (observe — tur
6171
+ AÇMAZ, maliyet yok): ajan dahili sunucunun çalıştığını bilir ve statik
6172
+ dosyalar için kendi sunucusunu başlatma denemez → çakışma biter */
6173
+ function bcTellServe(staticBase) {
6174
+ try {
6175
+ const ws = ideRoot();
6176
+ const sid = bcSessions.get(ws);
6177
+ if (!sid) return;
6178
+ engine.observe(sid,
6179
+ '[PREVIEW] Kullanıcı önizlemeyi açtı — DAHİLİ STATİK SUNUCU şu adreste ÇALIŞIYOR: ' + staticBase + '\n' +
6180
+ 'Statik dosyalar için KENDİ sunucunu BAŞLATMA; üretilen siteyi bu adres üzerinden değerlendir.\n' +
6181
+ 'Yalnızca gerçek dev-server/build gerekiyorsa (React/Vite/Next/Expo: npm run dev, expo start) ' +
6182
+ 'kendi sunucunu BLOKLAMADAN arka planda başlat ve çalışan adresi yaz.'
6183
+ );
6184
+ } catch {}
6185
+ }
6186
+
6187
+ ipcMain.handle('ide:previewFile', async (_e, rel) => {
5799
6188
  try {
5800
6189
  const p = ideSafe(rel);
5801
6190
  if (!p) return { ok: false, error: 'geçersiz yol' };
5802
6191
  if (!/\.html?$/i.test(p)) return { ok: false, error: 'önizleme yalnız .html/.htm dosyaları için' };
6192
+ const base = await bcStaticStart(ideRoot());
6193
+ if (!base) return { ok: false, error: 'statik sunucu başlatılamadı' };
6194
+ const root = ideRoot();
6195
+ const relPath = path.relative(root, p).replace(/\\/g, '/');
6196
+ const url = base + '/' + relPath;
5803
6197
  setBrowserOpen(true, true);
5804
- const url = 'file:///' + p.replace(/\\/g, '/');
5805
6198
  browser.view.webContents.loadURL(url).catch(() => {});
5806
6199
  browserEmit({ open: true, width: browser.width, url });
5807
- return { ok: true };
6200
+ return { ok: true, url };
5808
6201
  } catch (e) {
5809
6202
  return { ok: false, error: String((e && e.message) || e) };
5810
6203
  }
5811
6204
  });
5812
6205
 
5813
- /* PREVIEW: workspace kökündeki entry sayfayı (index.html ilk *.html) sağdaki
5814
- dahili tarayıcıda aç. file:// yükleme browserNavigate'i BYPASS eder o https
5815
- olmayan adresi aramaya çevirir. */
5816
- ipcMain.handle('ide:preview', () => { try {
6206
+ /* PREVIEW: workspace kökündeki siteyi DAİMA sunucudan
6207
+ 1) ajan bir dev server başlattıysa onun adresi, 2) yoksa dahili statik sunucu.
6208
+ file:// ASLA kullanılmaz (JS/clean URL kırılmaları). Ayrıca ajana SUNUCU
6209
+ komutu düşer: uygulama sunucu istiyorsa kendisi başlatıp adresi yazar. */
6210
+ ipcMain.handle('ide:preview', async () => {
6211
+ try {
5817
6212
  const root = ideRoot();
5818
6213
  const pick = (name) => {
5819
6214
  const p = path.join(root, name);
@@ -5828,12 +6223,19 @@ ipcMain.handle('ide:preview', () => { try {
5828
6223
  entry = htmls.length ? path.join(root, htmls.sort()[0]) : null;
5829
6224
  }
5830
6225
  if (!entry) return { ok: false, error: 'workspace kökünde index.html yok — önce agent\'a siteyi yazdır' };
5831
- /* forceVisible: preview'a basınca tarayıcı ikonuna basmaya gerek kalmasın
5832
- dahili tarayıcı otomatik ve GÖRÜNÜR açılır */
6226
+ /* forceVisible: preview'a basınca tarayıcı ikonuna basmaya gerek kalmasın */
5833
6227
  setBrowserOpen(true, true);
5834
- const url = 'file:///' + entry.replace(/\\/g, '/');
6228
+ let url = '';
6229
+ if (bcLastServerUrl) {
6230
+ url = bcLastServerUrl; /* ajanın kendi dev sunucusu öncelikli */
6231
+ } else {
6232
+ const base = await bcStaticStart(root);
6233
+ if (!base) return { ok: false, error: 'statik sunucu başlatılamadı' };
6234
+ url = base + '/';
6235
+ }
5835
6236
  browser.view.webContents.loadURL(url).catch(() => {});
5836
6237
  browserEmit({ open: true, width: browser.width, url });
6238
+ if (!bcLastServerUrl) bcTellServe(url.replace(/\/$/, ''));
5837
6239
  return { ok: true, url };
5838
6240
  } catch (e) {
5839
6241
  return { ok: false, error: String((e && e.message) || e) };
@@ -5841,20 +6243,44 @@ ipcMain.handle('ide:preview', () => { try {
5841
6243
  });
5842
6244
 
5843
6245
  /* BEAST CODE otomatik canlı önizleme: iş bitince üretilen site/app dahili
5844
- tarayıcıda GÖRÜNÜR açılır. Yalnız localhost + file:// kabul edilir —
5845
- dış adresler güvenlik için reddedilir. */
5846
- ipcMain.handle('ide:previewUrl', (_e, url) => {
6246
+ tarayıcıda GÖRÜNÜR açılır. Yalnız localhost kabul edilir; file:// gelen
6247
+ eser adresi DAHİLİ STATİK SUNUCUYA çevrilir — dosyadan ASLA açılmaz. */
6248
+ ipcMain.handle('ide:previewUrl', async (_e, url) => {
5847
6249
  try {
5848
6250
  const u = String(url || '');
5849
- const okUrl =
5850
- /^https?:\/\/(?:localhost|127\.0\.0\.1)(?::\d{2,5})?(?:\/|$)/i.test(u) ||
5851
- /^file:\/\/\//i.test(u);
5852
- if (!okUrl) return { ok: false, error: 'yalnız localhost/file:// adresleri önizlenebilir' };
5853
- /* forceVisible: preview'a basmaya gerek kalmasın — otomatik ve GÖRÜNÜR açılır */
6251
+ let target = '';
6252
+ if (/^https?:\/\/(?:localhost|127\.0\.0\.1)(?::\d{2,5})?(?:\/|$)/i.test(u)) {
6253
+ target = u;
6254
+ } else if (/^file:\/\/\//i.test(u)) {
6255
+ const raw = decodeURIComponent(u.replace(/^file:\/\/\//, '')).replace(/\/$/, '');
6256
+ const fp = path.resolve(raw);
6257
+ const root = ideRoot();
6258
+ const rel = path.relative(root, fp).replace(/\\/g, '/');
6259
+ let base;
6260
+ if (rel && !rel.startsWith('..')) {
6261
+ base = await bcStaticStart(root);
6262
+ if (!base) return { ok: false, error: 'statik sunucu başlatılamadı' };
6263
+ target = base + '/' + rel;
6264
+ } else {
6265
+ /* workspace dışı eser — dosyanın kendi klasörü kök alınır */
6266
+ base = await bcStaticStart(path.dirname(fp));
6267
+ if (!base) return { ok: false, error: 'statik sunucu başlatılamadı' };
6268
+ target = base + '/' + path.basename(fp);
6269
+ }
6270
+ } else {
6271
+ return { ok: false, error: 'yalnız localhost adresleri önizlenebilir' };
6272
+ }
6273
+ /* Expo/Metro dev sunucusu → telefon modu OTOMATİK (mobil uygulama canlı önizleme) */
6274
+ if (/^https?:\/\/(?:localhost|127\.0\.0\.1):(8081|19000|19001|19002|3000|5173)\//i.test(target)) {
6275
+ if (!browser.phone) setBrowserPhone(true);
6276
+ }
6277
+ /* forceVisible: otomatik ve GÖRÜNÜR açılır */
5854
6278
  setBrowserOpen(true, true);
5855
- browser.view.webContents.loadURL(u).catch(() => {});
5856
- browserEmit({ open: true, width: browser.width, url: u });
5857
- return { ok: true, url: u };
6279
+ browser.view.webContents.loadURL(target).catch(() => {});
6280
+ let wNow = 0;
6281
+ try { wNow = win.getContentSize()[0]; } catch {}
6282
+ browserEmit({ open: true, width: browserShownWidth(wNow), url: target });
6283
+ return { ok: true, url: target };
5858
6284
  } catch (e) {
5859
6285
  return { ok: false, error: String((e && e.message) || e) };
5860
6286
  }