beast-agent 1.4.2 → 1.5.1
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 +509 -89
- 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 +478 -61
- package/src/renderer/style.css +291 -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();
|
|
@@ -2711,8 +2747,7 @@ function falloutResume() {
|
|
|
2711
2747
|
if (!f.enabled || f.autoResume === false) return;
|
|
2712
2748
|
const st = JSON.parse(fs.readFileSync(FALLOUT_CRASH_FILE, 'utf8'));
|
|
2713
2749
|
if (!st || !st.sessionId) return;
|
|
2714
|
-
const
|
|
2715
|
-
const sid = exists ? st.sessionId : engine.createSession().id;
|
|
2750
|
+
const sid = reuseOrLatestSession(st.sessionId);
|
|
2716
2751
|
const when = st.at ? new Date(st.at).toLocaleString('tr-TR') : '?';
|
|
2717
2752
|
setTimeout(() => {
|
|
2718
2753
|
try {
|
|
@@ -2759,7 +2794,8 @@ function whereWasISummary() {
|
|
|
2759
2794
|
function maybeRunWhereWasI() {
|
|
2760
2795
|
try {
|
|
2761
2796
|
const cfg = settings.whereWasI || {};
|
|
2762
|
-
|
|
2797
|
+
/* DEFAULT KAPALI: yalnız Ayarlar → Maliyet · Limit'ten açıldıysa göster */
|
|
2798
|
+
if (cfg.enabled !== true) return;
|
|
2763
2799
|
const sum = whereWasISummary();
|
|
2764
2800
|
if (!sum) return;
|
|
2765
2801
|
settingsLog(`nerede-kaldım: ${String(sum.text).split('\n')[0]}`);
|
|
@@ -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 = {};
|
|
@@ -4840,8 +4922,12 @@ ipcMain.handle('setup:skip', () => {
|
|
|
4840
4922
|
return { ok: true };
|
|
4841
4923
|
});
|
|
4842
4924
|
|
|
4843
|
-
/* #5 "nerede kaldım" */
|
|
4844
|
-
ipcMain.handle('wherewasi:get', () =>
|
|
4925
|
+
/* #5 "nerede kaldım": get → gerçek ayar durumu (checkbox doğru görünsün) */
|
|
4926
|
+
ipcMain.handle('wherewasi:get', () => {
|
|
4927
|
+
const cfg = settings.whereWasI || {};
|
|
4928
|
+
const sum = whereWasISummary();
|
|
4929
|
+
return { enabled: cfg.enabled === true, ...(sum || {}) };
|
|
4930
|
+
});
|
|
4845
4931
|
ipcMain.handle('wherewasi:set', (_e, cfg) => {
|
|
4846
4932
|
settings.whereWasI = { enabled: !!(cfg && cfg.enabled) };
|
|
4847
4933
|
saveSettings();
|
|
@@ -5121,18 +5207,86 @@ function cronEmit() {
|
|
|
5121
5207
|
}
|
|
5122
5208
|
}
|
|
5123
5209
|
|
|
5210
|
+
/* Cron cevabı bekleme haritası: sid -> job. İş 'done' olunca cevap
|
|
5211
|
+
SAHİBİN bağlı olduğu TÜM AKTİF entegrasyonlara yansıtılır (kullanıcı
|
|
5212
|
+
hangi kanaldan ajanla iletişimde belli değil). */
|
|
5213
|
+
const cronAnswerPending = new Map();
|
|
5214
|
+
|
|
5215
|
+
/* OTOMATİK YENİ SOHBET YOK: cron/izleyici/fallout tetiklendiğinde oturum
|
|
5216
|
+
seçimi — kayıtlı id geçerliyse O, değilse EN GÜNCEL oturum kullanılır.
|
|
5217
|
+
Hiç oturum yoksa (ilk kurulum) yeni açılır. Böylece soldaki sohbet
|
|
5218
|
+
geçmişine "+ Yeni Sohbet" olmadan hayalet sohbetler düşmez. */
|
|
5219
|
+
function reuseOrLatestSession(preferredId) {
|
|
5220
|
+
const sid = String(preferredId || '');
|
|
5221
|
+
if (sid) {
|
|
5222
|
+
try {
|
|
5223
|
+
if (engine._load(sid)) return sid;
|
|
5224
|
+
} catch {}
|
|
5225
|
+
}
|
|
5226
|
+
try {
|
|
5227
|
+
const list = engine.listSessions(); // updatedAt'e göre yeni→eski sıralı
|
|
5228
|
+
if (list.length) return String(list[0].id);
|
|
5229
|
+
} catch {}
|
|
5230
|
+
return engine.createSession().id;
|
|
5231
|
+
}
|
|
5232
|
+
|
|
5233
|
+
/* Yansıtma hedefleri: bağlı WA (owner numarası), Telegram ve Discord
|
|
5234
|
+
(owner işaretli kayıt; tek kayıt varsa o). Cron oturumunun KENDİ
|
|
5235
|
+
kanalına yansıtmayız — cevabı zaten kendi akışından alır (çift yok). */
|
|
5236
|
+
function tgOwnerIds() {
|
|
5237
|
+
const list = settings.tgAllow || [];
|
|
5238
|
+
const objs = list.filter((e) => e && typeof e === 'object' && e.id && e.id !== '*');
|
|
5239
|
+
const owner = objs.find((e) => e.owner) || (objs.length === 1 ? objs[0] : null);
|
|
5240
|
+
return owner ? [String(owner.id)] : [];
|
|
5241
|
+
}
|
|
5242
|
+
function dcOwnerIds() {
|
|
5243
|
+
const list = settings.dcAllow || [];
|
|
5244
|
+
const objs = list.filter((e) => e && typeof e === 'object' && e.id && e.id !== '*');
|
|
5245
|
+
const owner = objs.find((e) => e.owner) || (objs.length === 1 ? objs[0] : null);
|
|
5246
|
+
return owner ? [String(owner.id)] : [];
|
|
5247
|
+
}
|
|
5248
|
+
function cronMirrorTargets(cronSid) {
|
|
5249
|
+
const out = [];
|
|
5250
|
+
try {
|
|
5251
|
+
if (wa && wa.connected) {
|
|
5252
|
+
const own = waOwnerNum();
|
|
5253
|
+
if (own) {
|
|
5254
|
+
const jid = own + '@s.whatsapp.net';
|
|
5255
|
+
const bound = [...waChats.entries()].some(([j, s]) => j === jid && String(s) === String(cronSid));
|
|
5256
|
+
if (!bound) out.push({ kind: 'wa', send: (t) => sendWaSafe(jid, t) });
|
|
5257
|
+
}
|
|
5258
|
+
}
|
|
5259
|
+
} catch {}
|
|
5260
|
+
try {
|
|
5261
|
+
if (tg && tg.connected) {
|
|
5262
|
+
for (const id of tgOwnerIds()) {
|
|
5263
|
+
const bound = [...tgChats.entries()].some(([c, s]) => String(c) === String(id) && String(s) === String(cronSid));
|
|
5264
|
+
if (!bound) out.push({ kind: 'tg', send: (t) => sendTgSafe(id, t) });
|
|
5265
|
+
}
|
|
5266
|
+
}
|
|
5267
|
+
} catch {}
|
|
5268
|
+
try {
|
|
5269
|
+
if (dc && dc.connected) {
|
|
5270
|
+
for (const id of dcOwnerIds()) {
|
|
5271
|
+
const bound = [...dcChats.entries()].some(([c, s]) => String(c) === String(id) && String(s) === String(cronSid));
|
|
5272
|
+
if (!bound) out.push({ kind: 'dc', send: (t) => sendDcSafe(id, t) });
|
|
5273
|
+
}
|
|
5274
|
+
}
|
|
5275
|
+
} catch {}
|
|
5276
|
+
return out;
|
|
5277
|
+
}
|
|
5278
|
+
|
|
5124
5279
|
function cronFire(job) {
|
|
5125
5280
|
try {
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
if (!exists) {
|
|
5129
|
-
const s = engine.createSession();
|
|
5130
|
-
sid = s.id;
|
|
5281
|
+
const sid = reuseOrLatestSession(job.sessionId);
|
|
5282
|
+
if (sid !== String(job.sessionId || '')) {
|
|
5131
5283
|
cron.update(job.id, { sessionId: sid });
|
|
5132
5284
|
}
|
|
5133
|
-
|
|
5285
|
+
cronAnswerPending.set(String(sid), job);
|
|
5286
|
+
const sent = engine.send(sid, {
|
|
5134
5287
|
text: `[cron: ${job.name}]\n${job.prompt}`,
|
|
5135
5288
|
});
|
|
5289
|
+
if (!sent) cronAnswerPending.delete(String(sid)); // gönderilemedi — bayat bekleme bırakma
|
|
5136
5290
|
} catch {}
|
|
5137
5291
|
cronEmit();
|
|
5138
5292
|
}
|
|
@@ -5141,10 +5295,8 @@ function cronFire(job) {
|
|
|
5141
5295
|
function watcherFire(w, value) {
|
|
5142
5296
|
try {
|
|
5143
5297
|
watcherLog(`tetiklendi id=${w.id} name="${w.name}" kind=${w.kind} op=${w.op} value=${value}`);
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
if (!exists) {
|
|
5147
|
-
sid = engine.createSession().id;
|
|
5298
|
+
const sid = reuseOrLatestSession(w.sessionId);
|
|
5299
|
+
if (sid !== String(w.sessionId || '')) {
|
|
5148
5300
|
watchers.patch(w.id, { sessionId: sid });
|
|
5149
5301
|
}
|
|
5150
5302
|
const target =
|
|
@@ -5274,9 +5426,35 @@ ipcMain.handle('browser:setWidth', (_e, wpx) => {
|
|
|
5274
5426
|
settings.browserWidth = browser.width;
|
|
5275
5427
|
saveSettings();
|
|
5276
5428
|
layoutBrowser();
|
|
5277
|
-
browserEmit({ open: true, width:
|
|
5429
|
+
browserEmit({ open: true, width: browserShownWidth(w) });
|
|
5278
5430
|
return { ok: true, width: browser.width };
|
|
5279
5431
|
});
|
|
5432
|
+
|
|
5433
|
+
/* TELEFON MODU: mobil UA + dar dock (≈430px) → aynı sitenin mobil versiyonu.
|
|
5434
|
+
Kapatınca masaüstü UA + kayıtlı genişlik geri gelir. UA değişimi için sayfa
|
|
5435
|
+
taze yüklenir. Expo/Metro dev sunucularında otomatik açılır. */
|
|
5436
|
+
function setBrowserPhone(on) {
|
|
5437
|
+
const want = !!on;
|
|
5438
|
+
if (browser.phone === want) return { ok: true, phone: want };
|
|
5439
|
+
browser.phone = want;
|
|
5440
|
+
try {
|
|
5441
|
+
const wc = browser.view && browser.view.webContents;
|
|
5442
|
+
if (wc) {
|
|
5443
|
+
wc.setUserAgent(browser.phone ? PHONE_UA : browser.desktopUA || PHONE_UA);
|
|
5444
|
+
let url = '';
|
|
5445
|
+
try { url = wc.getURL(); } catch {}
|
|
5446
|
+
if (url && /^https?:/i.test(url)) wc.loadURL(url).catch(() => {});
|
|
5447
|
+
}
|
|
5448
|
+
} catch {}
|
|
5449
|
+
applyPhoneEmulation(); // açık: 390x844 cihaz ortalanır — kapalı: emülasyon kapanır
|
|
5450
|
+
if (browser.open && win && !win.isDestroyed()) {
|
|
5451
|
+
layoutBrowser();
|
|
5452
|
+
const [w] = win.getContentSize();
|
|
5453
|
+
browserEmit({ open: true, width: browserShownWidth(w) });
|
|
5454
|
+
}
|
|
5455
|
+
return { ok: true, phone: browser.phone };
|
|
5456
|
+
}
|
|
5457
|
+
ipcMain.handle('browser:phone', (_e, on) => setBrowserPhone(on));
|
|
5280
5458
|
ipcMain.handle('browser:screenshot', async () => {
|
|
5281
5459
|
const r = await browserScreenshot();
|
|
5282
5460
|
if (!r.ok) return { ok: false, error: r.error };
|
|
@@ -5287,31 +5465,95 @@ ipcMain.handle('browser:screenshot', async () => {
|
|
|
5287
5465
|
Sağ dock paneli renderer'da; main sadece PowerShell komutlarını
|
|
5288
5466
|
çalıştırır ve çıktıyı canlı akıtır. Terminal ile tarayıcı aynı
|
|
5289
5467
|
dock'u paylaştığı için ikisi aynı anda açık kalamaz. */
|
|
5290
|
-
let termChild = null;
|
|
5468
|
+
let termChild = null; /* KALICI CMD oturumu — cd/set değişkenleri komutlar arasında KORUNUR */
|
|
5469
|
+
let termShellCwd = ''; /* izlenen çalışma klasörü (komut sonundaki marker'dan) */
|
|
5470
|
+
let termShellId = null;
|
|
5471
|
+
let termShellSeq = 0;
|
|
5472
|
+
let termForwarded = 0;
|
|
5473
|
+
let termCapNotified = false;
|
|
5291
5474
|
const TERM_FWD_CAP = 1024 * 1024; /* iletilecek çıktı üst sınırı (1 MB) */
|
|
5475
|
+
const TERM_MARKER = '__BEAST_EOF__';
|
|
5292
5476
|
|
|
5293
5477
|
function termSend(ev) {
|
|
5294
5478
|
if (win && !win.isDestroyed()) win.webContents.send('agent:event', ev);
|
|
5295
5479
|
}
|
|
5296
5480
|
|
|
5481
|
+
/* Kalıcı kabuğu başlat: cmd /q /k — girdi pipe'ından satır satır okur,
|
|
5482
|
+
prompt yazmaz. chcp 65001 → Türkçe yollar (Masaüstü vb.) doğru çözülür. */
|
|
5483
|
+
function termShellSpawn() {
|
|
5484
|
+
const cwd =
|
|
5485
|
+
termShellCwd ||
|
|
5486
|
+
(engine && engine.workspace) ||
|
|
5487
|
+
settings.workspace ||
|
|
5488
|
+
app.getPath('home');
|
|
5489
|
+
const child = spawn('cmd.exe', ['/q', '/k'], {
|
|
5490
|
+
cwd,
|
|
5491
|
+
windowsHide: true,
|
|
5492
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
5493
|
+
});
|
|
5494
|
+
termShellCwd = cwd;
|
|
5495
|
+
termChild = child;
|
|
5496
|
+
termForwarded = 0;
|
|
5497
|
+
termCapNotified = false;
|
|
5498
|
+
child.stdout.setEncoding('utf8');
|
|
5499
|
+
child.stderr.setEncoding('utf8');
|
|
5500
|
+
child.stdout.on('data', (d) => {
|
|
5501
|
+
termForwarded += String(d).length;
|
|
5502
|
+
if (termForwarded > TERM_FWD_CAP) {
|
|
5503
|
+
if (!termCapNotified) {
|
|
5504
|
+
termCapNotified = true;
|
|
5505
|
+
termSend({ type: 'term-out', id: termShellId, stream: 'out', chunk: '\n[beast] çıktı çok büyük — iletim durduruldu (komut sürüyor)\n' });
|
|
5506
|
+
}
|
|
5507
|
+
return;
|
|
5508
|
+
}
|
|
5509
|
+
termSend({ type: 'term-out', id: termShellId, stream: 'out', chunk: String(d) });
|
|
5510
|
+
});
|
|
5511
|
+
child.stderr.on('data', (d) => {
|
|
5512
|
+
termSend({ type: 'term-out', id: termShellId, stream: 'err', chunk: String(d) });
|
|
5513
|
+
});
|
|
5514
|
+
child.on('exit', () => {
|
|
5515
|
+
if (termChild === child) termChild = null;
|
|
5516
|
+
});
|
|
5517
|
+
/* UTF-8 kod sayfası: Türkçe karakterli klasörler/çıktılar bozulmadan akar.
|
|
5518
|
+
(cmd her stdin satırını bir komut işler — bu ilk satır chcp olur) */
|
|
5519
|
+
try { child.stdin.write('chcp 65001 > nul\r\n'); } catch {}
|
|
5520
|
+
}
|
|
5521
|
+
|
|
5297
5522
|
ipcMain.handle('terminal:toggle', () => {
|
|
5298
5523
|
if (browser.open) setBrowserOpen(false);
|
|
5299
|
-
return { ok: true, cwd: (engine && engine.workspace) || settings.workspace || app.getPath('home') };
|
|
5524
|
+
return { ok: true, cwd: termShellCwd || (engine && engine.workspace) || settings.workspace || app.getPath('home') };
|
|
5300
5525
|
});
|
|
5301
5526
|
|
|
5302
5527
|
ipcMain.handle('terminal:run', (_e, payload) => {
|
|
5303
5528
|
const cmd = String((payload && payload.cmd) || '').trim();
|
|
5304
5529
|
const shell = String((payload && payload.shell) || 'cmd');
|
|
5305
5530
|
if (!cmd) return { ok: false, error: 'boş komut' };
|
|
5306
|
-
|
|
5307
|
-
|
|
5531
|
+
/* kalıcı CMD: komut AYNI kabuğa yazılır → cd/set kalıcıdır; komutlar sıraya girer */
|
|
5532
|
+
if (shell === 'cmd') {
|
|
5533
|
+
if (!termChild || !termChild.stdin.writable) termShellSpawn();
|
|
5534
|
+
if (!termChild) return { ok: false, error: 'kabuk başlatılamadı' };
|
|
5535
|
+
const id = 't' + Date.now().toString(36) + ++termShellSeq;
|
|
5536
|
+
termShellId = id;
|
|
5537
|
+
termForwarded = 0;
|
|
5538
|
+
termCapNotified = false;
|
|
5539
|
+
try {
|
|
5540
|
+
/* İKİ AYRI satır: cmd her satırı SIRAYLA işler — ikinci satırdaki %CD%
|
|
5541
|
+
ancak ilk komut BİTİNCE okunur/genişletilir → doğru (yeni) klasör gelir.
|
|
5542
|
+
Aynı satıra & echo yazsaydık %CD% eski klasörü verirdi. */
|
|
5543
|
+
termChild.stdin.write(cmd + '\r\n' + 'echo ' + TERM_MARKER + '%CD%' + TERM_MARKER + '\r\n');
|
|
5544
|
+
} catch (e) {
|
|
5545
|
+
return { ok: false, error: String((e && e.message) || e) };
|
|
5546
|
+
}
|
|
5547
|
+
return { ok: true, id };
|
|
5548
|
+
}
|
|
5549
|
+
/* geriye dönük uyumluluk — UI artık yalnız CMD gönderir */
|
|
5550
|
+
const cwd = termShellCwd || (engine && engine.workspace) || settings.workspace || app.getPath('home');
|
|
5308
5551
|
const id = 't' + Date.now().toString(36);
|
|
5309
5552
|
let file, args;
|
|
5310
5553
|
if (shell === 'cmd') {
|
|
5311
5554
|
file = 'cmd.exe';
|
|
5312
5555
|
args = ['/d', '/s', '/c', cmd];
|
|
5313
5556
|
} else {
|
|
5314
|
-
/* geriye dönük uyumluluk — UI artık yalnız CMD gönderir */
|
|
5315
5557
|
file = 'powershell.exe';
|
|
5316
5558
|
args = ['-NoProfile', '-NoLogo', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', cmd];
|
|
5317
5559
|
}
|
|
@@ -5321,38 +5563,29 @@ ipcMain.handle('terminal:run', (_e, payload) => {
|
|
|
5321
5563
|
} catch (e) {
|
|
5322
5564
|
return { ok: false, error: String((e && e.message) || e) };
|
|
5323
5565
|
}
|
|
5324
|
-
termChild = child;
|
|
5325
5566
|
child.stdout.setEncoding('utf8');
|
|
5326
5567
|
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));
|
|
5568
|
+
child.stdout.on('data', (d) => termSend({ type: 'term-out', id, stream: 'out', chunk: String(d) }));
|
|
5569
|
+
child.stderr.on('data', (d) => termSend({ type: 'term-out', id, stream: 'err', chunk: String(d) }));
|
|
5342
5570
|
child.on('error', (err) => {
|
|
5343
|
-
if (termChild === child) termChild = null;
|
|
5344
5571
|
termSend({ type: 'term-end', id, code: -1, error: String((err && err.message) || err) });
|
|
5345
5572
|
});
|
|
5346
5573
|
child.on('close', (code) => {
|
|
5347
|
-
if (termChild === child) termChild = null;
|
|
5348
5574
|
termSend({ type: 'term-end', id, code: code == null ? -1 : code });
|
|
5349
5575
|
});
|
|
5350
5576
|
return { ok: true, id };
|
|
5351
5577
|
});
|
|
5352
5578
|
|
|
5353
5579
|
ipcMain.handle('terminal:stop', () => {
|
|
5354
|
-
|
|
5355
|
-
|
|
5580
|
+
/* ■: süren komutu (tüm alt süreçleriyle) kes; KALICI kabuk yenilenir —
|
|
5581
|
+
son bilinen klasör korunur, cd geçmişi kaybolmaz */
|
|
5582
|
+
if (termChild) {
|
|
5583
|
+
try { spawn('taskkill', ['/pid', String(termChild.pid), '/T', '/F'], { windowsHide: true }); } catch {}
|
|
5584
|
+
try { termChild.kill(); } catch {}
|
|
5585
|
+
termChild = null;
|
|
5586
|
+
}
|
|
5587
|
+
termSend({ type: 'term-end', id: termShellId, code: 130, error: 'komut durduruldu — kalıcı CMD yeniden hazır' });
|
|
5588
|
+
termShellId = null;
|
|
5356
5589
|
return { ok: true };
|
|
5357
5590
|
});
|
|
5358
5591
|
|
|
@@ -5405,13 +5638,16 @@ function bcPanelEvent(sid, ev) {
|
|
|
5405
5638
|
const BC_DEBOUNCE_MS = 900;
|
|
5406
5639
|
const bcQueue = new Map(); /* klasör yolu → { timer, msgs[] } */
|
|
5407
5640
|
|
|
5408
|
-
function bcQueuePush(ws, text) {
|
|
5641
|
+
function bcQueuePush(ws, text, attachments) {
|
|
5409
5642
|
let q = bcQueue.get(ws);
|
|
5410
5643
|
if (!q) {
|
|
5411
5644
|
q = { timer: null, msgs: [] };
|
|
5412
5645
|
bcQueue.set(ws, q);
|
|
5413
5646
|
}
|
|
5414
|
-
q.msgs.push({
|
|
5647
|
+
q.msgs.push({
|
|
5648
|
+
text,
|
|
5649
|
+
attachments: Array.isArray(attachments) && attachments.length ? attachments : undefined,
|
|
5650
|
+
});
|
|
5415
5651
|
return q;
|
|
5416
5652
|
}
|
|
5417
5653
|
|
|
@@ -5421,8 +5657,12 @@ function bcFlush(folder) {
|
|
|
5421
5657
|
const s = bcGetSession(folder); /* oturum yoksa oluşturur */
|
|
5422
5658
|
if (engine.isBusy(s.id)) return; /* hâlâ çalışıyor — done/error eventini bekle */
|
|
5423
5659
|
let merged = '';
|
|
5424
|
-
|
|
5425
|
-
|
|
5660
|
+
let mergedAtts = null;
|
|
5661
|
+
for (const m of q.msgs) {
|
|
5662
|
+
if (m.text) merged += (merged ? '\n' : '') + m.text;
|
|
5663
|
+
if (!mergedAtts && Array.isArray(m.attachments) && m.attachments.length) mergedAtts = m.attachments;
|
|
5664
|
+
}
|
|
5665
|
+
if (!merged.trim() && !mergedAtts) {
|
|
5426
5666
|
bcQueue.delete(folder);
|
|
5427
5667
|
clearTimeout(q.timer);
|
|
5428
5668
|
return;
|
|
@@ -5430,7 +5670,8 @@ function bcFlush(folder) {
|
|
|
5430
5670
|
s.workspace = folder;
|
|
5431
5671
|
s.bcCode = true;
|
|
5432
5672
|
engine.cache.set(s.id, s);
|
|
5433
|
-
|
|
5673
|
+
const payload = mergedAtts ? { text: merged, attachments: mergedAtts } : merged;
|
|
5674
|
+
if (engine.send(s.id, payload, { userAction: true })) {
|
|
5434
5675
|
bcQueue.delete(folder);
|
|
5435
5676
|
clearTimeout(q.timer);
|
|
5436
5677
|
} else {
|
|
@@ -5461,7 +5702,8 @@ function bcFlushOnDone(ev) {
|
|
|
5461
5702
|
|
|
5462
5703
|
ipcMain.handle('beastcode:send', async (_e, payload) => {
|
|
5463
5704
|
const text = String((payload && payload.msg) || '').trim();
|
|
5464
|
-
|
|
5705
|
+
const attachments = Array.isArray(payload && payload.attachments) ? payload.attachments : [];
|
|
5706
|
+
if (!text && !attachments.length) return { ok: false, error: 'boş mesaj' };
|
|
5465
5707
|
if (!engine) return { ok: false, error: 'ajan hazır değil' };
|
|
5466
5708
|
const ws = ideRoot();
|
|
5467
5709
|
const modeM = /^\/(plan|build|auto)\b/i.exec(text);
|
|
@@ -5489,18 +5731,36 @@ ipcMain.handle('beastcode:send', async (_e, payload) => {
|
|
|
5489
5731
|
}
|
|
5490
5732
|
if (busy) {
|
|
5491
5733
|
/* ajan çalışıyor → kuyruğa al; iş bitince (done/error) toplu gider */
|
|
5492
|
-
const q = bcQueuePush(ws, text);
|
|
5734
|
+
const q = bcQueuePush(ws, text, attachments);
|
|
5493
5735
|
return { ok: true, queued: true, count: q.msgs.length, sessionId: s.id };
|
|
5494
5736
|
}
|
|
5495
5737
|
/* boşta: kısa pencere — hızlı ard arda mesajlar tek işte birleşir */
|
|
5496
|
-
const q = bcQueuePush(ws, text);
|
|
5738
|
+
const q = bcQueuePush(ws, text, attachments);
|
|
5497
5739
|
clearTimeout(q.timer);
|
|
5498
5740
|
q.timer = setTimeout(() => { try { bcFlush(ws); } catch {} }, BC_DEBOUNCE_MS);
|
|
5499
5741
|
return { ok: true, sessionId: s.id, pending: true };
|
|
5500
5742
|
});
|
|
5501
5743
|
|
|
5502
|
-
|
|
5503
|
-
|
|
5744
|
+
/* BC görev listesi (ID'li) + TEK TUŞ GERİ ALMA: bir görev maddesinin
|
|
5745
|
+
değişiklikleri, madde başlamadan önceki kod tabanına döndürülür */
|
|
5746
|
+
ipcMain.handle('bc:todos', (_e, payload) => {
|
|
5747
|
+
try {
|
|
5748
|
+
return engine.todoUndoInfo(String((payload && payload.sessionId) || ''));
|
|
5749
|
+
} catch (e) {
|
|
5750
|
+
return { ok: false, error: String((e && e.message) || e) };
|
|
5751
|
+
}
|
|
5752
|
+
});
|
|
5753
|
+
ipcMain.handle('bc:undo', (_e, payload) => {
|
|
5754
|
+
try {
|
|
5755
|
+
const sid = String((payload && payload.sessionId) || '');
|
|
5756
|
+
const todoId = String((payload && payload.todoId) || '');
|
|
5757
|
+
return todoId === 'last' ? engine.undoLastTodo(sid) : engine.undoTodo(sid, todoId);
|
|
5758
|
+
} catch (e) {
|
|
5759
|
+
return { ok: false, error: String((e && e.message) || e) };
|
|
5760
|
+
}
|
|
5761
|
+
});
|
|
5762
|
+
|
|
5763
|
+
ipcMain.handle('beastcode:stop', async () => { const ws = ideRoot();
|
|
5504
5764
|
const sid = bcSessions.get(ws);
|
|
5505
5765
|
/* durdurma: bekleyen kuyruğu da boşalt (kullanıcı vazgeçti) */
|
|
5506
5766
|
const q = bcQueue.get(ws);
|
|
@@ -5527,6 +5787,7 @@ ipcMain.handle('beastcode:new', async () => {
|
|
|
5527
5787
|
}
|
|
5528
5788
|
if (sid) {
|
|
5529
5789
|
try { engine.deleteSession(sid); } catch {}
|
|
5790
|
+
bcLastServerUrl = ''; /* yeni oturum — eski dev server adresi geçersiz */
|
|
5530
5791
|
bcSessions.delete(ws);
|
|
5531
5792
|
}
|
|
5532
5793
|
return { ok: true };
|
|
@@ -5727,6 +5988,7 @@ ipcMain.handle('ide:setroot', async () => {
|
|
|
5727
5988
|
if (r.canceled || !r.filePaths || !r.filePaths[0]) return { ok: false, canceled: true };
|
|
5728
5989
|
settings.ideRoot = r.filePaths[0];
|
|
5729
5990
|
saveSettings();
|
|
5991
|
+
ideWatchStart(); // yeni kökte izleme yeniden kurulur
|
|
5730
5992
|
return { ok: true, root: settings.ideRoot };
|
|
5731
5993
|
} catch (e) {
|
|
5732
5994
|
return { ok: false, error: String((e && e.message) || e) };
|
|
@@ -5741,6 +6003,56 @@ function ideSafe(rel) {
|
|
|
5741
6003
|
return p;
|
|
5742
6004
|
}
|
|
5743
6005
|
|
|
6006
|
+
/* ---------- IDE DOSYA AĞACI CANLI İZLEME ----------
|
|
6007
|
+
Soldaki klasör paneli ELLE yenilemeden güncellensin: workspace kökü
|
|
6008
|
+
fs.watch (recursive) ile izlenir; node_modules/.git gürültüsü elenir,
|
|
6009
|
+
500ms debounce ile renderer'a 'ide-tree-changed' düşer. Ajan dışında
|
|
6010
|
+
(kullanıcı kaydı, git, harici program) değişen dosyalar da yakalanır. */
|
|
6011
|
+
let ideWatcher = null;
|
|
6012
|
+
let ideWatchTimer = null;
|
|
6013
|
+
let ideWatchRoot = '';
|
|
6014
|
+
function ideWatchStart() {
|
|
6015
|
+
const root = ideRoot();
|
|
6016
|
+
if (ideWatcher && ideWatchRoot === root) return;
|
|
6017
|
+
ideWatchStop();
|
|
6018
|
+
/* ev dizininin KÖKÜNÜ izlemek AppData gürültüsü yüzünden paneli sürekli
|
|
6019
|
+
yeniler — özel klasör seçiliyken (ideRoot/workspace) izleme aktiftir */
|
|
6020
|
+
if (root === app.getPath('home')) return;
|
|
6021
|
+
try {
|
|
6022
|
+
fs.accessSync(root); // kök yoksa izleme kurma
|
|
6023
|
+
} catch {
|
|
6024
|
+
return;
|
|
6025
|
+
}
|
|
6026
|
+
ideWatchRoot = root;
|
|
6027
|
+
try {
|
|
6028
|
+
ideWatcher = fs.watch(root, { recursive: true }, (_evType, fname) => {
|
|
6029
|
+
const f = String(fname || '').replace(/\\/g, '/');
|
|
6030
|
+
if (/^(node_modules|\.git|dist|\.next|\.nuxt)(\/|$)/i.test(f)) return;
|
|
6031
|
+
if (ideWatchTimer) return;
|
|
6032
|
+
ideWatchTimer = setTimeout(() => {
|
|
6033
|
+
ideWatchTimer = null;
|
|
6034
|
+
try {
|
|
6035
|
+
if (win && !win.isDestroyed()) {
|
|
6036
|
+
win.webContents.send('agent:event', { type: 'ide-tree-changed' });
|
|
6037
|
+
}
|
|
6038
|
+
} catch {}
|
|
6039
|
+
}, 500);
|
|
6040
|
+
});
|
|
6041
|
+
ideWatcher.on('error', () => {
|
|
6042
|
+
ideWatchStop();
|
|
6043
|
+
/* kök klasör silinip yeniden yaratıldıysa kısa süre sonra tekrar dene */
|
|
6044
|
+
setTimeout(() => { try { ideWatchStart(); } catch {} }, 3000);
|
|
6045
|
+
});
|
|
6046
|
+
try { log.info('ide', 'ağaç izleme açık: ' + root); } catch {}
|
|
6047
|
+
} catch {}
|
|
6048
|
+
}
|
|
6049
|
+
function ideWatchStop() {
|
|
6050
|
+
if (ideWatcher) { try { ideWatcher.close(); } catch {} }
|
|
6051
|
+
ideWatcher = null;
|
|
6052
|
+
ideWatchRoot = '';
|
|
6053
|
+
if (ideWatchTimer) { clearTimeout(ideWatchTimer); ideWatchTimer = null; }
|
|
6054
|
+
}
|
|
6055
|
+
|
|
5744
6056
|
ipcMain.handle('ide:tree', (_e, rel) => {
|
|
5745
6057
|
const p = ideSafe(rel);
|
|
5746
6058
|
if (!p) return { ok: false, error: 'geçersiz yol' };
|
|
@@ -5818,25 +6130,102 @@ ipcMain.handle('ide:delete', async (_e, rel) => {
|
|
|
5818
6130
|
});
|
|
5819
6131
|
|
|
5820
6132
|
/* Sağ tık menüsü: HTML dosyasını dahili tarayıcıda GÖRÜNÜR aç */
|
|
5821
|
-
|
|
6133
|
+
/* ---------- BC DAHİLİ STATİK SUNUCU ----------
|
|
6134
|
+
Beast Code çıktısı ASLA file:// ile açılmaz: ES modülleri, fetch, ServiceWorker
|
|
6135
|
+
ve "clean URL" yolları file://'da çalışmaz. Her preview http://127.0.0.1 üzerinden
|
|
6136
|
+
servis edilir; ajan kendi dev sunucusunu başlattıysa o adres önceliklidir. */
|
|
6137
|
+
let bcLastServerUrl = ''; /* ajanın başlattığı dev server adresi (bc-preview'dan yakalanır) */
|
|
6138
|
+
const bcStatic = { server: null, root: '', base: '' };
|
|
6139
|
+
const BC_MIME = {
|
|
6140
|
+
'.html': 'text/html; charset=utf-8', '.htm': 'text/html; charset=utf-8',
|
|
6141
|
+
'.js': 'text/javascript; charset=utf-8', '.mjs': 'text/javascript; charset=utf-8',
|
|
6142
|
+
'.css': 'text/css; charset=utf-8', '.json': 'application/json; charset=utf-8',
|
|
6143
|
+
'.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif',
|
|
6144
|
+
'.svg': 'image/svg+xml', '.ico': 'image/x-icon', '.webp': 'image/webp', '.avif': 'image/avif',
|
|
6145
|
+
'.woff': 'font/woff', '.woff2': 'font/woff2', '.ttf': 'font/ttf', '.otf': 'font/otf',
|
|
6146
|
+
'.txt': 'text/plain; charset=utf-8', '.md': 'text/plain; charset=utf-8',
|
|
6147
|
+
'.map': 'application/json', '.wasm': 'application/wasm', '.pdf': 'application/pdf',
|
|
6148
|
+
'.mp3': 'audio/mpeg', '.wav': 'audio/wav', '.mp4': 'video/mp4', '.webm': 'video/webm',
|
|
6149
|
+
'.xml': 'application/xml', '.csv': 'text/csv',
|
|
6150
|
+
};
|
|
6151
|
+
function bcStaticHandler(req, res) {
|
|
6152
|
+
try {
|
|
6153
|
+
let rel = decodeURIComponent(String((req && req.url) || '/').split('?')[0]);
|
|
6154
|
+
rel = rel.replace(/^\/+/, '');
|
|
6155
|
+
const fp = path.resolve(bcStatic.root, rel);
|
|
6156
|
+
if (fp !== bcStatic.root && !fp.startsWith(bcStatic.root + path.sep)) {
|
|
6157
|
+
res.writeHead(403); res.end('forbidden'); return;
|
|
6158
|
+
}
|
|
6159
|
+
const isFile = (p) => { try { return fs.statSync(p).isFile() ? p : null; } catch { return null; } };
|
|
6160
|
+
let hit = isFile(fp);
|
|
6161
|
+
if (!hit) hit = isFile(path.join(fp, 'index.html')); /* dizin → index.html */
|
|
6162
|
+
if (!hit) hit = isFile(fp + '.html'); /* clean URL: /hakkinda → hakkinda.html */
|
|
6163
|
+
if (!hit) { res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' }); res.end('404 — /' + rel); return; }
|
|
6164
|
+
const ext = path.extname(hit).toLowerCase();
|
|
6165
|
+
res.writeHead(200, { 'Content-Type': BC_MIME[ext] || 'application/octet-stream', 'Cache-Control': 'no-store' });
|
|
6166
|
+
fs.createReadStream(hit).pipe(res);
|
|
6167
|
+
} catch (e) {
|
|
6168
|
+
try { res.writeHead(500); res.end('server error'); } catch {}
|
|
6169
|
+
}
|
|
6170
|
+
}
|
|
6171
|
+
function bcStaticStart(root) {
|
|
6172
|
+
return new Promise((resolve) => {
|
|
6173
|
+
const r = path.resolve(String(root || ''));
|
|
6174
|
+
if (bcStatic.server && bcStatic.root === r) { resolve(bcStatic.base); return; }
|
|
6175
|
+
if (bcStatic.server) { try { bcStatic.server.close(); } catch {} bcStatic.server = null; }
|
|
6176
|
+
const srv = http.createServer(bcStaticHandler);
|
|
6177
|
+
srv.on('error', () => resolve(''));
|
|
6178
|
+
srv.listen(0, '127.0.0.1', () => {
|
|
6179
|
+
bcStatic.server = srv;
|
|
6180
|
+
bcStatic.root = r;
|
|
6181
|
+
bcStatic.base = 'http://127.0.0.1:' + srv.address().port;
|
|
6182
|
+
try { log.info('bc', 'statik sunucu: ' + bcStatic.base + ' → ' + r); } catch {}
|
|
6183
|
+
resolve(bcStatic.base);
|
|
6184
|
+
});
|
|
6185
|
+
});
|
|
6186
|
+
}
|
|
6187
|
+
/* Preview'a basılınca BC oturumuna SESSİZ bağlam enjeksiyonu (observe — tur
|
|
6188
|
+
AÇMAZ, maliyet yok): ajan dahili sunucunun çalıştığını bilir ve statik
|
|
6189
|
+
dosyalar için kendi sunucusunu başlatma denemez → çakışma biter */
|
|
6190
|
+
function bcTellServe(staticBase) {
|
|
6191
|
+
try {
|
|
6192
|
+
const ws = ideRoot();
|
|
6193
|
+
const sid = bcSessions.get(ws);
|
|
6194
|
+
if (!sid) return;
|
|
6195
|
+
engine.observe(sid,
|
|
6196
|
+
'[PREVIEW] Kullanıcı önizlemeyi açtı — DAHİLİ STATİK SUNUCU şu adreste ÇALIŞIYOR: ' + staticBase + '\n' +
|
|
6197
|
+
'Statik dosyalar için KENDİ sunucunu BAŞLATMA; üretilen siteyi bu adres üzerinden değerlendir.\n' +
|
|
6198
|
+
'Yalnızca gerçek dev-server/build gerekiyorsa (React/Vite/Next/Expo: npm run dev, expo start) ' +
|
|
6199
|
+
'kendi sunucunu BLOKLAMADAN arka planda başlat ve çalışan adresi yaz.'
|
|
6200
|
+
);
|
|
6201
|
+
} catch {}
|
|
6202
|
+
}
|
|
6203
|
+
|
|
6204
|
+
ipcMain.handle('ide:previewFile', async (_e, rel) => {
|
|
5822
6205
|
try {
|
|
5823
6206
|
const p = ideSafe(rel);
|
|
5824
6207
|
if (!p) return { ok: false, error: 'geçersiz yol' };
|
|
5825
6208
|
if (!/\.html?$/i.test(p)) return { ok: false, error: 'önizleme yalnız .html/.htm dosyaları için' };
|
|
6209
|
+
const base = await bcStaticStart(ideRoot());
|
|
6210
|
+
if (!base) return { ok: false, error: 'statik sunucu başlatılamadı' };
|
|
6211
|
+
const root = ideRoot();
|
|
6212
|
+
const relPath = path.relative(root, p).replace(/\\/g, '/');
|
|
6213
|
+
const url = base + '/' + relPath;
|
|
5826
6214
|
setBrowserOpen(true, true);
|
|
5827
|
-
const url = 'file:///' + p.replace(/\\/g, '/');
|
|
5828
6215
|
browser.view.webContents.loadURL(url).catch(() => {});
|
|
5829
6216
|
browserEmit({ open: true, width: browser.width, url });
|
|
5830
|
-
return { ok: true };
|
|
6217
|
+
return { ok: true, url };
|
|
5831
6218
|
} catch (e) {
|
|
5832
6219
|
return { ok: false, error: String((e && e.message) || e) };
|
|
5833
6220
|
}
|
|
5834
6221
|
});
|
|
5835
6222
|
|
|
5836
|
-
/* PREVIEW: workspace kökündeki
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
6223
|
+
/* PREVIEW: workspace kökündeki siteyi DAİMA sunucudan aç —
|
|
6224
|
+
1) ajan bir dev server başlattıysa onun adresi, 2) yoksa dahili statik sunucu.
|
|
6225
|
+
file:// ASLA kullanılmaz (JS/clean URL kırılmaları). Ayrıca ajana SUNUCU
|
|
6226
|
+
komutu düşer: uygulama sunucu istiyorsa kendisi başlatıp adresi yazar. */
|
|
6227
|
+
ipcMain.handle('ide:preview', async () => {
|
|
6228
|
+
try {
|
|
5840
6229
|
const root = ideRoot();
|
|
5841
6230
|
const pick = (name) => {
|
|
5842
6231
|
const p = path.join(root, name);
|
|
@@ -5851,12 +6240,19 @@ ipcMain.handle('ide:preview', () => { try {
|
|
|
5851
6240
|
entry = htmls.length ? path.join(root, htmls.sort()[0]) : null;
|
|
5852
6241
|
}
|
|
5853
6242
|
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 */
|
|
6243
|
+
/* forceVisible: preview'a basınca tarayıcı ikonuna basmaya gerek kalmasın */
|
|
5856
6244
|
setBrowserOpen(true, true);
|
|
5857
|
-
|
|
6245
|
+
let url = '';
|
|
6246
|
+
if (bcLastServerUrl) {
|
|
6247
|
+
url = bcLastServerUrl; /* ajanın kendi dev sunucusu öncelikli */
|
|
6248
|
+
} else {
|
|
6249
|
+
const base = await bcStaticStart(root);
|
|
6250
|
+
if (!base) return { ok: false, error: 'statik sunucu başlatılamadı' };
|
|
6251
|
+
url = base + '/';
|
|
6252
|
+
}
|
|
5858
6253
|
browser.view.webContents.loadURL(url).catch(() => {});
|
|
5859
6254
|
browserEmit({ open: true, width: browser.width, url });
|
|
6255
|
+
if (!bcLastServerUrl) bcTellServe(url.replace(/\/$/, ''));
|
|
5860
6256
|
return { ok: true, url };
|
|
5861
6257
|
} catch (e) {
|
|
5862
6258
|
return { ok: false, error: String((e && e.message) || e) };
|
|
@@ -5864,20 +6260,44 @@ ipcMain.handle('ide:preview', () => { try {
|
|
|
5864
6260
|
});
|
|
5865
6261
|
|
|
5866
6262
|
/* 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) => {
|
|
6263
|
+
tarayıcıda GÖRÜNÜR açılır. Yalnız localhost kabul edilir; file:// gelen
|
|
6264
|
+
eser adresi DAHİLİ STATİK SUNUCUYA çevrilir — dosyadan ASLA açılmaz. */
|
|
6265
|
+
ipcMain.handle('ide:previewUrl', async (_e, url) => {
|
|
5870
6266
|
try {
|
|
5871
6267
|
const u = String(url || '');
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
if (
|
|
5876
|
-
|
|
6268
|
+
let target = '';
|
|
6269
|
+
if (/^https?:\/\/(?:localhost|127\.0\.0\.1)(?::\d{2,5})?(?:\/|$)/i.test(u)) {
|
|
6270
|
+
target = u;
|
|
6271
|
+
} else if (/^file:\/\/\//i.test(u)) {
|
|
6272
|
+
const raw = decodeURIComponent(u.replace(/^file:\/\/\//, '')).replace(/\/$/, '');
|
|
6273
|
+
const fp = path.resolve(raw);
|
|
6274
|
+
const root = ideRoot();
|
|
6275
|
+
const rel = path.relative(root, fp).replace(/\\/g, '/');
|
|
6276
|
+
let base;
|
|
6277
|
+
if (rel && !rel.startsWith('..')) {
|
|
6278
|
+
base = await bcStaticStart(root);
|
|
6279
|
+
if (!base) return { ok: false, error: 'statik sunucu başlatılamadı' };
|
|
6280
|
+
target = base + '/' + rel;
|
|
6281
|
+
} else {
|
|
6282
|
+
/* workspace dışı eser — dosyanın kendi klasörü kök alınır */
|
|
6283
|
+
base = await bcStaticStart(path.dirname(fp));
|
|
6284
|
+
if (!base) return { ok: false, error: 'statik sunucu başlatılamadı' };
|
|
6285
|
+
target = base + '/' + path.basename(fp);
|
|
6286
|
+
}
|
|
6287
|
+
} else {
|
|
6288
|
+
return { ok: false, error: 'yalnız localhost adresleri önizlenebilir' };
|
|
6289
|
+
}
|
|
6290
|
+
/* Expo/Metro dev sunucusu → telefon modu OTOMATİK (mobil uygulama canlı önizleme) */
|
|
6291
|
+
if (/^https?:\/\/(?:localhost|127\.0\.0\.1):(8081|19000|19001|19002|3000|5173)\//i.test(target)) {
|
|
6292
|
+
if (!browser.phone) setBrowserPhone(true);
|
|
6293
|
+
}
|
|
6294
|
+
/* forceVisible: otomatik ve GÖRÜNÜR açılır */
|
|
5877
6295
|
setBrowserOpen(true, true);
|
|
5878
|
-
browser.view.webContents.loadURL(
|
|
5879
|
-
|
|
5880
|
-
|
|
6296
|
+
browser.view.webContents.loadURL(target).catch(() => {});
|
|
6297
|
+
let wNow = 0;
|
|
6298
|
+
try { wNow = win.getContentSize()[0]; } catch {}
|
|
6299
|
+
browserEmit({ open: true, width: browserShownWidth(wNow), url: target });
|
|
6300
|
+
return { ok: true, url: target };
|
|
5881
6301
|
} catch (e) {
|
|
5882
6302
|
return { ok: false, error: String((e && e.message) || e) };
|
|
5883
6303
|
}
|