beast-agent 1.4.2 → 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 +478 -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 +477 -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) {
|
|
@@ -2032,7 +2037,7 @@ async function processWaMessage(jid, payload, senderNum, requeues = 0) {
|
|
|
2032
2037
|
text: waEcho.join('\n'),
|
|
2033
2038
|
});
|
|
2034
2039
|
}
|
|
2035
|
-
engine.send(sid, { text: text.slice(0, 8000), attachments });
|
|
2040
|
+
engine.send(sid, { text: text.slice(0, 8000), attachments }, { userAction: true });
|
|
2036
2041
|
}
|
|
2037
2042
|
|
|
2038
2043
|
function ensureWa() {
|
|
@@ -2241,7 +2246,7 @@ async function processTgMessage(chatId, payload, requeues = 0) {
|
|
|
2241
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.]`;
|
|
2242
2247
|
}
|
|
2243
2248
|
text += `\n${String(payload.text || '').slice(0, 6000)}`;
|
|
2244
|
-
engine.send(sid, { text: text.slice(0, 8000), attachments: [] });
|
|
2249
|
+
engine.send(sid, { text: text.slice(0, 8000), attachments: [] }, { userAction: true });
|
|
2245
2250
|
}
|
|
2246
2251
|
|
|
2247
2252
|
async function sendTgSafe(chatId, text) {
|
|
@@ -2459,7 +2464,7 @@ async function processDcMessage(channelId, payload) {
|
|
|
2459
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.]`;
|
|
2460
2465
|
}
|
|
2461
2466
|
text += `\n${String(payload.text || '').slice(0, 6000)}`;
|
|
2462
|
-
engine.send(sid, { text: text.slice(0, 8000), attachments: [] });
|
|
2467
|
+
engine.send(sid, { text: text.slice(0, 8000), attachments: [] }, { userAction: true });
|
|
2463
2468
|
}
|
|
2464
2469
|
|
|
2465
2470
|
async function sendDcSafe(channelId, text) {
|
|
@@ -2572,6 +2577,11 @@ function reloadBackend() {
|
|
|
2572
2577
|
if (win && !win.isDestroyed()) win.webContents.send('agent:event', ev);
|
|
2573
2578
|
flushDesktopOnDone(ev); /* biriken desktop mesajlarını sıraya bas */
|
|
2574
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
|
+
}
|
|
2575
2585
|
/* WA canlı iş takibi: oturum bir WhatsApp sohbetine bağlıysa araç
|
|
2576
2586
|
hareketlerini (terminal, web, dosya…) kısa satırla bildir.
|
|
2577
2587
|
Spam olmasın: sohbet başına en az 7 sn'de bir tek satır. */
|
|
@@ -2693,6 +2703,32 @@ function reloadBackend() {
|
|
|
2693
2703
|
})();
|
|
2694
2704
|
}
|
|
2695
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
|
+
}
|
|
2696
2732
|
},
|
|
2697
2733
|
});
|
|
2698
2734
|
return engine.publicState();
|
|
@@ -2855,6 +2891,7 @@ app.whenReady().then(() => {
|
|
|
2855
2891
|
cron.init({ onFire: cronFire });
|
|
2856
2892
|
watchers.start({ onTrigger: watcherFire });
|
|
2857
2893
|
startEventBus();
|
|
2894
|
+
ideWatchStart(); // soldaki dosya ağacı canlı izlemede
|
|
2858
2895
|
maybeRunWhereWasI();
|
|
2859
2896
|
falloutResume();
|
|
2860
2897
|
startAutoUpdater(); // #3 sessiz güncelleme
|
|
@@ -2938,18 +2975,57 @@ function showWin() {
|
|
|
2938
2975
|
|
|
2939
2976
|
const BROWSER_TOOLBAR_H = 46;
|
|
2940
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';
|
|
2941
2984
|
/* Tarayıcı state: visible=false → ajanlar GİZLİ kullanır (headless);
|
|
2942
2985
|
göz ikonuyla görünür mod açılır. open=view aktif, visible=panelde görünürlük */
|
|
2943
|
-
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: '' };
|
|
2944
2987
|
|
|
2945
2988
|
function browserEmit(payload) {
|
|
2946
|
-
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 });
|
|
2947
2990
|
}
|
|
2948
2991
|
|
|
2949
2992
|
function browserWidthFor(w) {
|
|
2950
2993
|
return Math.max(380, Math.min(800, Math.floor(w * 0.46)));
|
|
2951
2994
|
}
|
|
2952
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
|
+
|
|
2953
3029
|
function layoutBrowser() {
|
|
2954
3030
|
if (!win || win.isDestroyed()) return;
|
|
2955
3031
|
const [w, h] = win.getContentSize();
|
|
@@ -2960,7 +3036,7 @@ function layoutBrowser() {
|
|
|
2960
3036
|
const saved = Math.round(Number(settings.browserWidth) || 0);
|
|
2961
3037
|
browser.width = saved >= 300 ? saved : browserWidthFor(w);
|
|
2962
3038
|
}
|
|
2963
|
-
const shownWidth =
|
|
3039
|
+
const shownWidth = browserShownWidth(w);
|
|
2964
3040
|
if (!browser.view || !browser.open) return;
|
|
2965
3041
|
const view = browser.view;
|
|
2966
3042
|
if (!browser.attached) {
|
|
@@ -2972,6 +3048,7 @@ function layoutBrowser() {
|
|
|
2972
3048
|
try {
|
|
2973
3049
|
view.setBounds({ x: Math.max(0, w - shownWidth), y: BROWSER_TOOLBAR_H, width: shownWidth, height: Math.max(0, h - BROWSER_TOOLBAR_H) });
|
|
2974
3050
|
view.setVisible(browser.open && browser.visible);
|
|
3051
|
+
if (browser.phone) applyPhoneEmulation(); // pencere boyutlanınca telefon ölçeği tazelensin
|
|
2975
3052
|
} catch {}
|
|
2976
3053
|
}
|
|
2977
3054
|
|
|
@@ -2998,6 +3075,8 @@ function ensureBrowser() {
|
|
|
2998
3075
|
const bses = session.fromPartition('persist:browser');
|
|
2999
3076
|
if (bses && bses.setUserAgent) bses.setUserAgent(chromeUA, 'tr-TR,tr;q=0.9,en;q=0.8');
|
|
3000
3077
|
wc.setUserAgent(chromeUA);
|
|
3078
|
+
browser.desktopUA = chromeUA;
|
|
3079
|
+
if (browser.phone) wc.setUserAgent(PHONE_UA); // telefon modu açıkken mobil UA ile doğ
|
|
3001
3080
|
} catch {}
|
|
3002
3081
|
wc.setWindowOpenHandler(({ url }) => {
|
|
3003
3082
|
if (/^https?:\/\//i.test(url)) wc.loadURL(url).catch(() => {});
|
|
@@ -3022,6 +3101,7 @@ function ensureBrowser() {
|
|
|
3022
3101
|
browser.view = null;
|
|
3023
3102
|
});
|
|
3024
3103
|
browser.view = view;
|
|
3104
|
+
if (browser.phone) applyPhoneEmulation(); // tarayıcı telefon modunda doğarsa emülasyonu uygula
|
|
3025
3105
|
return view;
|
|
3026
3106
|
}
|
|
3027
3107
|
|
|
@@ -3923,6 +4003,8 @@ function createWindow() {
|
|
|
3923
4003
|
/* ---------------- IPC ---------------- */
|
|
3924
4004
|
|
|
3925
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());
|
|
3926
4008
|
|
|
3927
4009
|
/* #3 log sistemi: ayarlar → Log sekmesinde görüntülenir */
|
|
3928
4010
|
ipcMain.handle('logs:get', () => {
|
|
@@ -4295,7 +4377,7 @@ function handleGlobalStopStart(sessionId, cmd) {
|
|
|
4295
4377
|
const n = stopEverything();
|
|
4296
4378
|
reply =
|
|
4297
4379
|
`\u25A0 **Durdu** — ${n} koşan iş kesildi.\n` +
|
|
4298
|
-
'
|
|
4380
|
+
'Sürüyen sorgular, akıştaki cevaplar ve ajan faaliyetleri ANINDA kesildi; ajan yeni sorgu da açamaz.\n' +
|
|
4299
4381
|
'Devam için bir şeyler yaz ya da `/start`';
|
|
4300
4382
|
} else {
|
|
4301
4383
|
const wasPaused = servicesPaused;
|
|
@@ -4392,7 +4474,7 @@ async function flushDesktop(sessionId) {
|
|
|
4392
4474
|
if (!mergedAtts && Array.isArray(m.attachments)) mergedAtts = m.attachments;
|
|
4393
4475
|
}
|
|
4394
4476
|
if (!mergedText.trim() && !mergedAtts) return;
|
|
4395
|
-
engine.send(sid, mergedAtts ? { text: mergedText, attachments: mergedAtts } : mergedText);
|
|
4477
|
+
engine.send(sid, mergedAtts ? { text: mergedText, attachments: mergedAtts } : mergedText, { userAction: true });
|
|
4396
4478
|
}
|
|
4397
4479
|
|
|
4398
4480
|
/* agent işi bitince biriken masaüstü mesajlarını göndere bastır.
|
|
@@ -4627,13 +4709,13 @@ ipcMain.handle('model:set', (_e, sel) => {
|
|
|
4627
4709
|
|
|
4628
4710
|
/* Paralel ajan geçmişini TOPLUCA sil (rail başlığındaki çöp ikonu) */
|
|
4629
4711
|
ipcMain.handle('agents:clearAll', () => {
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
});
|
|
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
|
+
});
|
|
4637
4719
|
|
|
4638
4720
|
ipcMain.handle('model:role', (_e, map) => {
|
|
4639
4721
|
const roleModels = {};
|
|
@@ -5121,6 +5203,57 @@ function cronEmit() {
|
|
|
5121
5203
|
}
|
|
5122
5204
|
}
|
|
5123
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
|
+
|
|
5124
5257
|
function cronFire(job) {
|
|
5125
5258
|
try {
|
|
5126
5259
|
let sid = job.sessionId;
|
|
@@ -5130,9 +5263,11 @@ function cronFire(job) {
|
|
|
5130
5263
|
sid = s.id;
|
|
5131
5264
|
cron.update(job.id, { sessionId: sid });
|
|
5132
5265
|
}
|
|
5133
|
-
|
|
5266
|
+
cronAnswerPending.set(String(sid), job);
|
|
5267
|
+
const sent = engine.send(sid, {
|
|
5134
5268
|
text: `[cron: ${job.name}]\n${job.prompt}`,
|
|
5135
5269
|
});
|
|
5270
|
+
if (!sent) cronAnswerPending.delete(String(sid)); // gönderilemedi — bayat bekleme bırakma
|
|
5136
5271
|
} catch {}
|
|
5137
5272
|
cronEmit();
|
|
5138
5273
|
}
|
|
@@ -5274,9 +5409,35 @@ ipcMain.handle('browser:setWidth', (_e, wpx) => {
|
|
|
5274
5409
|
settings.browserWidth = browser.width;
|
|
5275
5410
|
saveSettings();
|
|
5276
5411
|
layoutBrowser();
|
|
5277
|
-
browserEmit({ open: true, width:
|
|
5412
|
+
browserEmit({ open: true, width: browserShownWidth(w) });
|
|
5278
5413
|
return { ok: true, width: browser.width };
|
|
5279
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));
|
|
5280
5441
|
ipcMain.handle('browser:screenshot', async () => {
|
|
5281
5442
|
const r = await browserScreenshot();
|
|
5282
5443
|
if (!r.ok) return { ok: false, error: r.error };
|
|
@@ -5287,31 +5448,95 @@ ipcMain.handle('browser:screenshot', async () => {
|
|
|
5287
5448
|
Sağ dock paneli renderer'da; main sadece PowerShell komutlarını
|
|
5288
5449
|
çalıştırır ve çıktıyı canlı akıtır. Terminal ile tarayıcı aynı
|
|
5289
5450
|
dock'u paylaştığı için ikisi aynı anda açık kalamaz. */
|
|
5290
|
-
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;
|
|
5291
5457
|
const TERM_FWD_CAP = 1024 * 1024; /* iletilecek çıktı üst sınırı (1 MB) */
|
|
5458
|
+
const TERM_MARKER = '__BEAST_EOF__';
|
|
5292
5459
|
|
|
5293
5460
|
function termSend(ev) {
|
|
5294
5461
|
if (win && !win.isDestroyed()) win.webContents.send('agent:event', ev);
|
|
5295
5462
|
}
|
|
5296
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
|
+
|
|
5297
5505
|
ipcMain.handle('terminal:toggle', () => {
|
|
5298
5506
|
if (browser.open) setBrowserOpen(false);
|
|
5299
|
-
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') };
|
|
5300
5508
|
});
|
|
5301
5509
|
|
|
5302
5510
|
ipcMain.handle('terminal:run', (_e, payload) => {
|
|
5303
5511
|
const cmd = String((payload && payload.cmd) || '').trim();
|
|
5304
5512
|
const shell = String((payload && payload.shell) || 'cmd');
|
|
5305
5513
|
if (!cmd) return { ok: false, error: 'boş komut' };
|
|
5306
|
-
|
|
5307
|
-
|
|
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');
|
|
5308
5534
|
const id = 't' + Date.now().toString(36);
|
|
5309
5535
|
let file, args;
|
|
5310
5536
|
if (shell === 'cmd') {
|
|
5311
5537
|
file = 'cmd.exe';
|
|
5312
5538
|
args = ['/d', '/s', '/c', cmd];
|
|
5313
5539
|
} else {
|
|
5314
|
-
/* geriye dönük uyumluluk — UI artık yalnız CMD gönderir */
|
|
5315
5540
|
file = 'powershell.exe';
|
|
5316
5541
|
args = ['-NoProfile', '-NoLogo', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', cmd];
|
|
5317
5542
|
}
|
|
@@ -5321,38 +5546,29 @@ ipcMain.handle('terminal:run', (_e, payload) => {
|
|
|
5321
5546
|
} catch (e) {
|
|
5322
5547
|
return { ok: false, error: String((e && e.message) || e) };
|
|
5323
5548
|
}
|
|
5324
|
-
termChild = child;
|
|
5325
5549
|
child.stdout.setEncoding('utf8');
|
|
5326
5550
|
child.stderr.setEncoding('utf8');
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
const push = (stream, d) => {
|
|
5330
|
-
if (forwarded > TERM_FWD_CAP) {
|
|
5331
|
-
if (!capNotified) {
|
|
5332
|
-
capNotified = true;
|
|
5333
|
-
termSend({ type: 'term-out', id, stream, chunk: '\n[beast] çıktı çok büyük — iletim durduruldu (komut sürüyor)\n' });
|
|
5334
|
-
}
|
|
5335
|
-
return;
|
|
5336
|
-
}
|
|
5337
|
-
forwarded += String(d).length;
|
|
5338
|
-
termSend({ type: 'term-out', id, stream, chunk: String(d) });
|
|
5339
|
-
};
|
|
5340
|
-
child.stdout.on('data', (d) => push('out', d));
|
|
5341
|
-
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) }));
|
|
5342
5553
|
child.on('error', (err) => {
|
|
5343
|
-
if (termChild === child) termChild = null;
|
|
5344
5554
|
termSend({ type: 'term-end', id, code: -1, error: String((err && err.message) || err) });
|
|
5345
5555
|
});
|
|
5346
5556
|
child.on('close', (code) => {
|
|
5347
|
-
if (termChild === child) termChild = null;
|
|
5348
5557
|
termSend({ type: 'term-end', id, code: code == null ? -1 : code });
|
|
5349
5558
|
});
|
|
5350
5559
|
return { ok: true, id };
|
|
5351
5560
|
});
|
|
5352
5561
|
|
|
5353
5562
|
ipcMain.handle('terminal:stop', () => {
|
|
5354
|
-
|
|
5355
|
-
|
|
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;
|
|
5356
5572
|
return { ok: true };
|
|
5357
5573
|
});
|
|
5358
5574
|
|
|
@@ -5405,13 +5621,16 @@ function bcPanelEvent(sid, ev) {
|
|
|
5405
5621
|
const BC_DEBOUNCE_MS = 900;
|
|
5406
5622
|
const bcQueue = new Map(); /* klasör yolu → { timer, msgs[] } */
|
|
5407
5623
|
|
|
5408
|
-
function bcQueuePush(ws, text) {
|
|
5624
|
+
function bcQueuePush(ws, text, attachments) {
|
|
5409
5625
|
let q = bcQueue.get(ws);
|
|
5410
5626
|
if (!q) {
|
|
5411
5627
|
q = { timer: null, msgs: [] };
|
|
5412
5628
|
bcQueue.set(ws, q);
|
|
5413
5629
|
}
|
|
5414
|
-
q.msgs.push({
|
|
5630
|
+
q.msgs.push({
|
|
5631
|
+
text,
|
|
5632
|
+
attachments: Array.isArray(attachments) && attachments.length ? attachments : undefined,
|
|
5633
|
+
});
|
|
5415
5634
|
return q;
|
|
5416
5635
|
}
|
|
5417
5636
|
|
|
@@ -5421,8 +5640,12 @@ function bcFlush(folder) {
|
|
|
5421
5640
|
const s = bcGetSession(folder); /* oturum yoksa oluşturur */
|
|
5422
5641
|
if (engine.isBusy(s.id)) return; /* hâlâ çalışıyor — done/error eventini bekle */
|
|
5423
5642
|
let merged = '';
|
|
5424
|
-
|
|
5425
|
-
|
|
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) {
|
|
5426
5649
|
bcQueue.delete(folder);
|
|
5427
5650
|
clearTimeout(q.timer);
|
|
5428
5651
|
return;
|
|
@@ -5430,7 +5653,8 @@ function bcFlush(folder) {
|
|
|
5430
5653
|
s.workspace = folder;
|
|
5431
5654
|
s.bcCode = true;
|
|
5432
5655
|
engine.cache.set(s.id, s);
|
|
5433
|
-
|
|
5656
|
+
const payload = mergedAtts ? { text: merged, attachments: mergedAtts } : merged;
|
|
5657
|
+
if (engine.send(s.id, payload, { userAction: true })) {
|
|
5434
5658
|
bcQueue.delete(folder);
|
|
5435
5659
|
clearTimeout(q.timer);
|
|
5436
5660
|
} else {
|
|
@@ -5461,7 +5685,8 @@ function bcFlushOnDone(ev) {
|
|
|
5461
5685
|
|
|
5462
5686
|
ipcMain.handle('beastcode:send', async (_e, payload) => {
|
|
5463
5687
|
const text = String((payload && payload.msg) || '').trim();
|
|
5464
|
-
|
|
5688
|
+
const attachments = Array.isArray(payload && payload.attachments) ? payload.attachments : [];
|
|
5689
|
+
if (!text && !attachments.length) return { ok: false, error: 'boş mesaj' };
|
|
5465
5690
|
if (!engine) return { ok: false, error: 'ajan hazır değil' };
|
|
5466
5691
|
const ws = ideRoot();
|
|
5467
5692
|
const modeM = /^\/(plan|build|auto)\b/i.exec(text);
|
|
@@ -5489,18 +5714,36 @@ ipcMain.handle('beastcode:send', async (_e, payload) => {
|
|
|
5489
5714
|
}
|
|
5490
5715
|
if (busy) {
|
|
5491
5716
|
/* ajan çalışıyor → kuyruğa al; iş bitince (done/error) toplu gider */
|
|
5492
|
-
const q = bcQueuePush(ws, text);
|
|
5717
|
+
const q = bcQueuePush(ws, text, attachments);
|
|
5493
5718
|
return { ok: true, queued: true, count: q.msgs.length, sessionId: s.id };
|
|
5494
5719
|
}
|
|
5495
5720
|
/* boşta: kısa pencere — hızlı ard arda mesajlar tek işte birleşir */
|
|
5496
|
-
const q = bcQueuePush(ws, text);
|
|
5721
|
+
const q = bcQueuePush(ws, text, attachments);
|
|
5497
5722
|
clearTimeout(q.timer);
|
|
5498
5723
|
q.timer = setTimeout(() => { try { bcFlush(ws); } catch {} }, BC_DEBOUNCE_MS);
|
|
5499
5724
|
return { ok: true, sessionId: s.id, pending: true };
|
|
5500
5725
|
});
|
|
5501
5726
|
|
|
5502
|
-
|
|
5503
|
-
|
|
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();
|
|
5504
5747
|
const sid = bcSessions.get(ws);
|
|
5505
5748
|
/* durdurma: bekleyen kuyruğu da boşalt (kullanıcı vazgeçti) */
|
|
5506
5749
|
const q = bcQueue.get(ws);
|
|
@@ -5527,6 +5770,7 @@ ipcMain.handle('beastcode:new', async () => {
|
|
|
5527
5770
|
}
|
|
5528
5771
|
if (sid) {
|
|
5529
5772
|
try { engine.deleteSession(sid); } catch {}
|
|
5773
|
+
bcLastServerUrl = ''; /* yeni oturum — eski dev server adresi geçersiz */
|
|
5530
5774
|
bcSessions.delete(ws);
|
|
5531
5775
|
}
|
|
5532
5776
|
return { ok: true };
|
|
@@ -5727,6 +5971,7 @@ ipcMain.handle('ide:setroot', async () => {
|
|
|
5727
5971
|
if (r.canceled || !r.filePaths || !r.filePaths[0]) return { ok: false, canceled: true };
|
|
5728
5972
|
settings.ideRoot = r.filePaths[0];
|
|
5729
5973
|
saveSettings();
|
|
5974
|
+
ideWatchStart(); // yeni kökte izleme yeniden kurulur
|
|
5730
5975
|
return { ok: true, root: settings.ideRoot };
|
|
5731
5976
|
} catch (e) {
|
|
5732
5977
|
return { ok: false, error: String((e && e.message) || e) };
|
|
@@ -5741,6 +5986,56 @@ function ideSafe(rel) {
|
|
|
5741
5986
|
return p;
|
|
5742
5987
|
}
|
|
5743
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
|
+
|
|
5744
6039
|
ipcMain.handle('ide:tree', (_e, rel) => {
|
|
5745
6040
|
const p = ideSafe(rel);
|
|
5746
6041
|
if (!p) return { ok: false, error: 'geçersiz yol' };
|
|
@@ -5818,25 +6113,102 @@ ipcMain.handle('ide:delete', async (_e, rel) => {
|
|
|
5818
6113
|
});
|
|
5819
6114
|
|
|
5820
6115
|
/* Sağ tık menüsü: HTML dosyasını dahili tarayıcıda GÖRÜNÜR aç */
|
|
5821
|
-
|
|
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) => {
|
|
5822
6188
|
try {
|
|
5823
6189
|
const p = ideSafe(rel);
|
|
5824
6190
|
if (!p) return { ok: false, error: 'geçersiz yol' };
|
|
5825
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;
|
|
5826
6197
|
setBrowserOpen(true, true);
|
|
5827
|
-
const url = 'file:///' + p.replace(/\\/g, '/');
|
|
5828
6198
|
browser.view.webContents.loadURL(url).catch(() => {});
|
|
5829
6199
|
browserEmit({ open: true, width: browser.width, url });
|
|
5830
|
-
return { ok: true };
|
|
6200
|
+
return { ok: true, url };
|
|
5831
6201
|
} catch (e) {
|
|
5832
6202
|
return { ok: false, error: String((e && e.message) || e) };
|
|
5833
6203
|
}
|
|
5834
6204
|
});
|
|
5835
6205
|
|
|
5836
|
-
/* PREVIEW: workspace kökündeki
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
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 {
|
|
5840
6212
|
const root = ideRoot();
|
|
5841
6213
|
const pick = (name) => {
|
|
5842
6214
|
const p = path.join(root, name);
|
|
@@ -5851,12 +6223,19 @@ ipcMain.handle('ide:preview', () => { try {
|
|
|
5851
6223
|
entry = htmls.length ? path.join(root, htmls.sort()[0]) : null;
|
|
5852
6224
|
}
|
|
5853
6225
|
if (!entry) return { ok: false, error: 'workspace kökünde index.html yok — önce agent\'a siteyi yazdır' };
|
|
5854
|
-
/* forceVisible: preview'a basınca tarayıcı ikonuna basmaya gerek kalmasın
|
|
5855
|
-
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 */
|
|
5856
6227
|
setBrowserOpen(true, true);
|
|
5857
|
-
|
|
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
|
+
}
|
|
5858
6236
|
browser.view.webContents.loadURL(url).catch(() => {});
|
|
5859
6237
|
browserEmit({ open: true, width: browser.width, url });
|
|
6238
|
+
if (!bcLastServerUrl) bcTellServe(url.replace(/\/$/, ''));
|
|
5860
6239
|
return { ok: true, url };
|
|
5861
6240
|
} catch (e) {
|
|
5862
6241
|
return { ok: false, error: String((e && e.message) || e) };
|
|
@@ -5864,20 +6243,44 @@ ipcMain.handle('ide:preview', () => { try {
|
|
|
5864
6243
|
});
|
|
5865
6244
|
|
|
5866
6245
|
/* BEAST CODE otomatik canlı önizleme: iş bitince üretilen site/app dahili
|
|
5867
|
-
tarayıcıda GÖRÜNÜR açılır. Yalnız localhost
|
|
5868
|
-
|
|
5869
|
-
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) => {
|
|
5870
6249
|
try {
|
|
5871
6250
|
const u = String(url || '');
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
if (
|
|
5876
|
-
|
|
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 */
|
|
5877
6278
|
setBrowserOpen(true, true);
|
|
5878
|
-
browser.view.webContents.loadURL(
|
|
5879
|
-
|
|
5880
|
-
|
|
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 };
|
|
5881
6284
|
} catch (e) {
|
|
5882
6285
|
return { ok: false, error: String((e && e.message) || e) };
|
|
5883
6286
|
}
|