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/package.json +1 -1
- package/src/agent/engine.js +239 -15
- package/src/agent/llm.js +62 -39
- package/src/main.js +501 -75
- package/src/preload.js +5 -1
- package/src/renderer/i18n.js +12 -2
- package/src/renderer/index.html +30 -7
- package/src/renderer/renderer.js +484 -60
- package/src/renderer/style.css +288 -20
package/src/main.js
CHANGED
|
@@ -863,10 +863,15 @@ function stopEverything() {
|
|
|
863
863
|
}
|
|
864
864
|
|
|
865
865
|
function resumeServices() {
|
|
866
|
-
if (!servicesPaused)
|
|
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
|
-
`
|
|
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
|
-
|
|
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 =
|
|
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
|
-
'
|
|
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
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
5284
|
-
|
|
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
|
-
|
|
5305
|
-
|
|
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
|
-
|
|
5332
|
-
|
|
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({
|
|
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
|
-
|
|
5402
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5480
|
-
|
|
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
|
-
|
|
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
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
6206
|
+
/* PREVIEW: workspace kökündeki siteyi DAİMA sunucudan aç —
|
|
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
|
-
|
|
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
|
|
5845
|
-
|
|
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
|
-
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
if (
|
|
5853
|
-
|
|
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(
|
|
5856
|
-
|
|
5857
|
-
|
|
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
|
}
|