beast-agent 2.11.0 → 2.13.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 +34 -0
- package/src/agent/headroom.js +279 -0
- package/src/agent/llm.js +17 -1
- package/src/agent/tools.js +15 -0
- package/src/main.js +586 -7
- package/src/preload.js +18 -0
- package/src/renderer/i18n.js +6 -2
- package/src/renderer/index.html +62 -3
- package/src/renderer/renderer.js +734 -89
- package/src/renderer/style.css +322 -0
package/src/main.js
CHANGED
|
@@ -27,6 +27,8 @@ const usageMod = require('./agent/usage');
|
|
|
27
27
|
const bus = require('./agent/bus');
|
|
28
28
|
const computeruse = require('./agent/computeruse');
|
|
29
29
|
const log = require('./agent/logger');
|
|
30
|
+
const headroom = require('./agent/headroom');
|
|
31
|
+
const QRCode = require('qrcode'); /* Expo Go QR (bc-expurl) — whatsapp ile aynı paket */
|
|
30
32
|
|
|
31
33
|
/* #3 otomatik updater: sessiz — indirir, kapanışta kurar, kullanıcıya soru sormaz.
|
|
32
34
|
Paketlenmemiş (npm start) modda devre dışı; Update sekmesi ve /update komutu kontrol eder. */
|
|
@@ -3103,11 +3105,16 @@ function reloadBackend() {
|
|
|
3103
3105
|
flushDesktopOnDone(ev); /* biriken desktop mesajlarını sıraya bas */
|
|
3104
3106
|
bcFlushOnDone(ev); /* Beast Code kuyruğunu iş bitiminde boşalt */
|
|
3105
3107
|
stFlushOnDone(ev); /* Beast Studio kuyruğunu iş bitiminde boşalt */
|
|
3108
|
+
sbFlushOnDone(ev); /* Sandbox kuyruğunu iş bitiminde boşalt */
|
|
3106
3109
|
/* BC canlı önizleme: ajan bir dev server başlattıysa adresi yakala —
|
|
3107
3110
|
preview butonu ve otomatik açılış DAİMA bu sunucuyu öncelikli kullanır */
|
|
3108
3111
|
if (ev.type === 'bc-preview' && /^https?:\/\/(?:localhost|127\.0\.0\.1)(?::\d{2,5})?/i.test(String(ev.url || ''))) {
|
|
3109
3112
|
bcLastServerUrl = String(ev.url);
|
|
3110
3113
|
}
|
|
3114
|
+
/* Expo Go QR: ajanın dev sunucusu exp:// adresi yazdıysa QR üretip panele bas */
|
|
3115
|
+
if (ev.type === 'bc-expurl') {
|
|
3116
|
+
bcExpQrMake(String(ev.url || '')).catch(() => {});
|
|
3117
|
+
}
|
|
3111
3118
|
/* WA canlı iş takibi: oturum bir WhatsApp sohbetine bağlıysa araç
|
|
3112
3119
|
hareketlerini (terminal, web, dosya…) kısa satırla bildir.
|
|
3113
3120
|
Spam olmasın: sohbet başına en az 7 sn'de bir tek satır. */
|
|
@@ -3284,6 +3291,33 @@ function reloadBackend() {
|
|
|
3284
3291
|
}
|
|
3285
3292
|
},
|
|
3286
3293
|
});
|
|
3294
|
+
/* PANEL RUN köprüsü: ajanın panel_run aracı → ÇALIŞTIR panelindeki yönetilen
|
|
3295
|
+
süreç koşucusu (sandbox:run IPC ile aynı makine). */
|
|
3296
|
+
engine.sbRunHook = sbRunStartManaged;
|
|
3297
|
+
|
|
3298
|
+
/* HEADROOM (opsiyonel token sıkıştırma proxy'si): engine zincirinden provider
|
|
3299
|
+
listesi alınır; durum değişince renderer'a 'headroom-status' düşer */
|
|
3300
|
+
headroom.setProviderGetter(() => {
|
|
3301
|
+
const seen = new Map();
|
|
3302
|
+
for (const c of (engine && engine.cfg && engine.cfg.chain) || []) {
|
|
3303
|
+
if (!c || !c.providerId || !c.url || seen.has(c.providerId)) continue;
|
|
3304
|
+
seen.set(c.providerId, {
|
|
3305
|
+
id: String(c.providerId),
|
|
3306
|
+
name: String(c.providerName || c.providerId),
|
|
3307
|
+
baseUrl: String(c.url).replace(/\/chat\/completions$/, ''),
|
|
3308
|
+
});
|
|
3309
|
+
}
|
|
3310
|
+
return [...seen.values()];
|
|
3311
|
+
});
|
|
3312
|
+
headroom.setStatusSink((st) => {
|
|
3313
|
+
try {
|
|
3314
|
+
if (win && !win.isDestroyed()) win.webContents.send('agent:event', { type: 'headroom-status', status: st });
|
|
3315
|
+
} catch {}
|
|
3316
|
+
});
|
|
3317
|
+
/* ayarlardan açık kaldıysa: uygulama açılışında arka planda başlat */
|
|
3318
|
+
if (settings.headroom && settings.headroom.enabled) {
|
|
3319
|
+
setTimeout(() => { headroom.setEnabled(true).catch(() => {}); }, 4000);
|
|
3320
|
+
}
|
|
3287
3321
|
return engine.publicState();
|
|
3288
3322
|
}
|
|
3289
3323
|
|
|
@@ -3512,6 +3546,7 @@ app.whenReady().then(() => {
|
|
|
3512
3546
|
flushBrowserStorage(); // x.com/google oturumları (cookies) diske yazılsın
|
|
3513
3547
|
try { toolsMod.disposeShellSessions(); } catch {} // kalıcı shell oturumlarını kapat
|
|
3514
3548
|
try { require('./agent/mcp').stopAll(); } catch {} // MCP server süreçlerini kapat
|
|
3549
|
+
try { headroom.stopAll(); } catch {} // headroom proxy süreçlerini kapat
|
|
3515
3550
|
});
|
|
3516
3551
|
|
|
3517
3552
|
if (process.argv.includes('--smoke')) {
|
|
@@ -6244,12 +6279,45 @@ ipcMain.handle('install:status', async () => {
|
|
|
6244
6279
|
/* 8) Edge TTS (bulut) */
|
|
6245
6280
|
rows.push({ id: 'edge', name: 'Edge TTS — seslendirme', state: 'cloud', detail: 'bulut — kurulum gerekmez' });
|
|
6246
6281
|
|
|
6282
|
+
/* 9) Headroom CLI — arka plan paket kurulumu burada görünür;
|
|
6283
|
+
aç/kapat Token Sıkıştırma sekmesinde */
|
|
6284
|
+
{
|
|
6285
|
+
const st = headroom.probe();
|
|
6286
|
+
let state = 'optional';
|
|
6287
|
+
if (st.installed) state = 'ok';
|
|
6288
|
+
else if (st.installing) state = 'loading';
|
|
6289
|
+
if (st.failed) state = 'failed';
|
|
6290
|
+
rows.push({
|
|
6291
|
+
id: 'headroom',
|
|
6292
|
+
name: 'Headroom CLI — token sıkıştırma motoru',
|
|
6293
|
+
state,
|
|
6294
|
+
detail: st.installing
|
|
6295
|
+
? 'arka planda kuruluyor — uv tool install headroom-ai[proxy]'
|
|
6296
|
+
: st.failed
|
|
6297
|
+
? (st.error || 'kurulum başarısız')
|
|
6298
|
+
: st.installed
|
|
6299
|
+
? 'kurulu — aç/kapat: Token Sıkıştırma sekmesi'
|
|
6300
|
+
: 'kurulu değil — Token Sıkıştırma sekmesinden açınca otomatik kurulur',
|
|
6301
|
+
...(st.installing ? pctFields('headroom') : {}),
|
|
6302
|
+
});
|
|
6303
|
+
}
|
|
6304
|
+
|
|
6247
6305
|
return rows;
|
|
6248
6306
|
});
|
|
6249
6307
|
|
|
6250
6308
|
/* cua-driver kurulumunu şimdi başlat (Kurulum sekmesi otomatiği + elle tetikleme) */
|
|
6251
6309
|
ipcMain.handle('cua:install', () => require('./agent/computeruse').autoInstall());
|
|
6252
6310
|
|
|
6311
|
+
/* HEADROOM (opsiyonel token sıkıştırma): aç/kapa + durum */
|
|
6312
|
+
ipcMain.handle('headroom:toggle', async (_e, on) => {
|
|
6313
|
+
settings.headroom = { ...(settings.headroom || {}), enabled: !!on };
|
|
6314
|
+
saveSettings();
|
|
6315
|
+
const r = await headroom.setEnabled(!!on).catch((e) => ({ ok: false, error: String((e && e.message) || e) }));
|
|
6316
|
+
return r;
|
|
6317
|
+
});
|
|
6318
|
+
ipcMain.handle('headroom:status', () => headroom.probe());
|
|
6319
|
+
ipcMain.handle('headroom:stats', () => headroom.stats());
|
|
6320
|
+
|
|
6253
6321
|
/* embedding modelini şimdi indir (mem0 arama yolu ısıtılır) */
|
|
6254
6322
|
ipcMain.handle('embed:prefetch', () => {
|
|
6255
6323
|
try {
|
|
@@ -7459,9 +7527,10 @@ ipcMain.handle('beastcode:send', async (_e, payload) => {
|
|
|
7459
7527
|
return { ok: true, sessionId: s.id, mode: s.bcMode };
|
|
7460
7528
|
}
|
|
7461
7529
|
if (busy) {
|
|
7462
|
-
/*
|
|
7463
|
-
|
|
7464
|
-
|
|
7530
|
+
/* OPENCODE STEER: koşan tur varken mesaj kuyrukta BEKLEMEZ — engine.send
|
|
7531
|
+
konuşmaya ekler, koşan tur sonraki istekte görür (ana chat ile aynı) */
|
|
7532
|
+
engine.send(s.id, attachments.length ? { text, attachments } : text, { userAction: true });
|
|
7533
|
+
return { ok: true, sessionId: s.id, steered: true };
|
|
7465
7534
|
}
|
|
7466
7535
|
/* boşta: kısa pencere — hızlı ard arda mesajlar tek işte birleşir */
|
|
7467
7536
|
const q = bcQueuePush(ws, text, attachments);
|
|
@@ -7745,8 +7814,9 @@ ipcMain.handle('studio:send', async (_e, payload) => {
|
|
|
7745
7814
|
return { ok: true, sessionId: s.id, mode: s.bcMode };
|
|
7746
7815
|
}
|
|
7747
7816
|
if (busy) {
|
|
7748
|
-
|
|
7749
|
-
|
|
7817
|
+
/* OPENCODE STEER: koşan tur varken mesaj kuyrukta beklemez — konuşmaya eklenir */
|
|
7818
|
+
engine.send(s.id, attachments.length ? { text, attachments } : text, { userAction: true });
|
|
7819
|
+
return { ok: true, sessionId: s.id, steered: true };
|
|
7750
7820
|
}
|
|
7751
7821
|
const q = stQueuePush(ws, text, attachments);
|
|
7752
7822
|
clearTimeout(q.timer);
|
|
@@ -8226,6 +8296,454 @@ ipcMain.handle('studio:delete', async (_e, rel) => {
|
|
|
8226
8296
|
}
|
|
8227
8297
|
});
|
|
8228
8298
|
|
|
8299
|
+
/* Sağ tık menüsü: HTML dosyasını dahili tarayıcıda GÖRÜNÜR aç */
|
|
8300
|
+
/* ---------- BEAST SANDBOX: GitHub repolarını indir & içinde çalış ----------
|
|
8301
|
+
Her repo KENDİ klasöründe (~/Beast-Sandbox/<ad>) ve KENDİ gizli oturumunda
|
|
8302
|
+
koşar (klasör bazlı, bcCode disiplinli — Beast Code motorunun aynısı; ajanın
|
|
8303
|
+
run_command/read/write araçları repo klasöründe çalışır). Ana sohbet VE
|
|
8304
|
+
Code/Studio panellerinden tamamen ayrıdır; ana chat solda yerinde kalır. */
|
|
8305
|
+
const sbSessions = new Map(); /* klasör → sessionId */
|
|
8306
|
+
const SB_DEBOUNCE_MS = 900;
|
|
8307
|
+
const sbQueue = new Map(); /* klasör → { timer, msgs[] } */
|
|
8308
|
+
|
|
8309
|
+
function sandboxRoot() {
|
|
8310
|
+
const dir = path.join(app.getPath('home'), 'Beast-Sandbox');
|
|
8311
|
+
try { fs.mkdirSync(dir, { recursive: true }); } catch {}
|
|
8312
|
+
return dir;
|
|
8313
|
+
}
|
|
8314
|
+
|
|
8315
|
+
function sbSessionFile() {
|
|
8316
|
+
return path.join(beastDir(), 'sandbox-sessions.json');
|
|
8317
|
+
}
|
|
8318
|
+
|
|
8319
|
+
let sbMapLoaded = false;
|
|
8320
|
+
function sbLoadMap() {
|
|
8321
|
+
if (sbMapLoaded) return;
|
|
8322
|
+
sbMapLoaded = true;
|
|
8323
|
+
try {
|
|
8324
|
+
const raw = JSON.parse(fs.readFileSync(sbSessionFile(), 'utf8'));
|
|
8325
|
+
for (const [k, v] of Object.entries(raw || {})) {
|
|
8326
|
+
if (k && typeof v === 'string') sbSessions.set(k, v);
|
|
8327
|
+
}
|
|
8328
|
+
} catch {}
|
|
8329
|
+
}
|
|
8330
|
+
|
|
8331
|
+
function sbSaveMap() {
|
|
8332
|
+
try {
|
|
8333
|
+
fs.writeFileSync(sbSessionFile(), JSON.stringify(Object.fromEntries(sbSessions), null, 2));
|
|
8334
|
+
} catch {}
|
|
8335
|
+
}
|
|
8336
|
+
|
|
8337
|
+
function sandboxGetSession(folder) {
|
|
8338
|
+
sbLoadMap();
|
|
8339
|
+
let sid = sbSessions.get(folder);
|
|
8340
|
+
if (sid) {
|
|
8341
|
+
try {
|
|
8342
|
+
const s = engine.cache.get(sid) || engine._load(sid);
|
|
8343
|
+
if (s) return s;
|
|
8344
|
+
} catch {}
|
|
8345
|
+
sbSessions.delete(folder);
|
|
8346
|
+
}
|
|
8347
|
+
const s = engine._load(engine.createSession().id);
|
|
8348
|
+
s.messages = s.messages || [];
|
|
8349
|
+
s.bgTitle = 'Beast Sandbox'; /* _view.isBg → sohbet geçmişinde gizli */
|
|
8350
|
+
s.bcCode = true; /* todo disiplini + hızlı iş kapanışı (engine) */
|
|
8351
|
+
s.sbSandbox = true; /* engine: SANDBOX sistem bloğu — ajan açık kaynak repoda olduğunu bilir */
|
|
8352
|
+
try {
|
|
8353
|
+
fs.appendFileSync(
|
|
8354
|
+
engine._file(s.id),
|
|
8355
|
+
JSON.stringify({ t: 'meta2', bgOf: '', title: 'Beast Sandbox', at: new Date().toISOString() }) + '\n'
|
|
8356
|
+
);
|
|
8357
|
+
} catch {}
|
|
8358
|
+
bcMarkWs(s, folder); /* klasör bağı oturum dosyasına yazılır */
|
|
8359
|
+
engine.cache.set(s.id, s);
|
|
8360
|
+
sbSessions.set(folder, s.id);
|
|
8361
|
+
sbSaveMap();
|
|
8362
|
+
return s;
|
|
8363
|
+
}
|
|
8364
|
+
|
|
8365
|
+
function sbQueuePush(ws, text, attachments) {
|
|
8366
|
+
let q = sbQueue.get(ws);
|
|
8367
|
+
if (!q) {
|
|
8368
|
+
q = { timer: null, msgs: [] };
|
|
8369
|
+
sbQueue.set(ws, q);
|
|
8370
|
+
}
|
|
8371
|
+
q.msgs.push({
|
|
8372
|
+
text,
|
|
8373
|
+
attachments: Array.isArray(attachments) && attachments.length ? attachments : undefined,
|
|
8374
|
+
});
|
|
8375
|
+
return q;
|
|
8376
|
+
}
|
|
8377
|
+
|
|
8378
|
+
function sbFlush(folder) {
|
|
8379
|
+
const q = sbQueue.get(folder);
|
|
8380
|
+
if (!q || !q.msgs.length) return;
|
|
8381
|
+
const s = sandboxGetSession(folder);
|
|
8382
|
+
if (engine.isBusy(s.id)) return; /* steer sistemine rağmen kuyruğu koru */
|
|
8383
|
+
let merged = '';
|
|
8384
|
+
let mergedAtts = null;
|
|
8385
|
+
for (const m of q.msgs) {
|
|
8386
|
+
if (m.text) merged += (merged ? '\n' : '') + m.text;
|
|
8387
|
+
if (!mergedAtts && Array.isArray(m.attachments) && m.attachments.length) mergedAtts = m.attachments;
|
|
8388
|
+
}
|
|
8389
|
+
if (!merged.trim() && !mergedAtts) {
|
|
8390
|
+
sbQueue.delete(folder);
|
|
8391
|
+
clearTimeout(q.timer);
|
|
8392
|
+
return;
|
|
8393
|
+
}
|
|
8394
|
+
s.workspace = folder;
|
|
8395
|
+
s.bcCode = true;
|
|
8396
|
+
s.sbSandbox = true;
|
|
8397
|
+
engine.cache.set(s.id, s);
|
|
8398
|
+
const payload = mergedAtts ? { text: merged, attachments: mergedAtts } : merged;
|
|
8399
|
+
engine.send(s.id, payload, { userAction: true });
|
|
8400
|
+
sbQueue.delete(folder);
|
|
8401
|
+
clearTimeout(q.timer);
|
|
8402
|
+
}
|
|
8403
|
+
|
|
8404
|
+
function sbFlushOnDone(ev) {
|
|
8405
|
+
if (!ev || (ev.type !== 'done' && ev.type !== 'error') || !ev.sessionId) return;
|
|
8406
|
+
for (const [folder, sid] of sbSessions) {
|
|
8407
|
+
if (String(sid) === String(ev.sessionId) && sbQueue.has(folder)) {
|
|
8408
|
+
setTimeout(() => { try { sbFlush(folder); } catch {} }, 150);
|
|
8409
|
+
}
|
|
8410
|
+
}
|
|
8411
|
+
}
|
|
8412
|
+
|
|
8413
|
+
/* repo adresten (url | owner/repo) normalize et */
|
|
8414
|
+
function sbRepoFromInput(input) {
|
|
8415
|
+
let s = String(input || '').trim().replace(/\.git$/, '');
|
|
8416
|
+
if (!s) return null;
|
|
8417
|
+
const m = /^(?:https?:\/\/)?(?:www\.)?github\.com\/([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)/i.exec(s);
|
|
8418
|
+
if (m) return { url: 'https://github.com/' + m[1] + '/' + m[2] + '.git', name: m[2] };
|
|
8419
|
+
if (/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(s)) {
|
|
8420
|
+
const parts = s.split('/');
|
|
8421
|
+
return { url: 'https://github.com/' + parts[0] + '/' + parts[1] + '.git', name: parts[1] };
|
|
8422
|
+
}
|
|
8423
|
+
return null;
|
|
8424
|
+
}
|
|
8425
|
+
|
|
8426
|
+
ipcMain.handle('sandbox:list', async () => {
|
|
8427
|
+
const root = sandboxRoot();
|
|
8428
|
+
const items = [];
|
|
8429
|
+
try {
|
|
8430
|
+
sbLoadMap();
|
|
8431
|
+
for (const e of fs.readdirSync(root, { withFileTypes: true })) {
|
|
8432
|
+
if (!e.isDirectory() || e.name.startsWith('.')) continue;
|
|
8433
|
+
const folder = path.join(root, e.name);
|
|
8434
|
+
let sid = '';
|
|
8435
|
+
let busy = false;
|
|
8436
|
+
const known = sbSessions.get(folder);
|
|
8437
|
+
if (known) {
|
|
8438
|
+
sid = String(known);
|
|
8439
|
+
try { busy = engine.isBusy(known); } catch {}
|
|
8440
|
+
}
|
|
8441
|
+
items.push({ name: e.name, folder, sid, busy });
|
|
8442
|
+
}
|
|
8443
|
+
} catch {}
|
|
8444
|
+
return { ok: true, root, items };
|
|
8445
|
+
});
|
|
8446
|
+
|
|
8447
|
+
ipcMain.handle('sandbox:clone', async (_e, input) => {
|
|
8448
|
+
const repo = sbRepoFromInput(input);
|
|
8449
|
+
if (!repo) return { ok: false, error: 'github.com/owner/repo, owner/repo ya da tam .git adresi gir' };
|
|
8450
|
+
const root = sandboxRoot();
|
|
8451
|
+
const base = String(repo.name || 'repo').replace(/[\\/:*?"<>|]/g, '_').slice(0, 60) || 'repo';
|
|
8452
|
+
let dest = path.join(root, base);
|
|
8453
|
+
let n = 2;
|
|
8454
|
+
while (fs.existsSync(dest)) dest = path.join(root, base + '-' + n++);
|
|
8455
|
+
const finalName = path.basename(dest);
|
|
8456
|
+
return new Promise((resolve) => {
|
|
8457
|
+
const { spawn } = require('child_process');
|
|
8458
|
+
let settled = false;
|
|
8459
|
+
let lastPct = -1;
|
|
8460
|
+
let lastEmit = 0;
|
|
8461
|
+
let errText = '';
|
|
8462
|
+
/* git ilerlemeyi stderr'a \r ile AYNI satıra yazar (Receiving objects: %34 …)
|
|
8463
|
+
— her karede yüzde yakalanır, panele canlı akar */
|
|
8464
|
+
const onChunk = (buf) => {
|
|
8465
|
+
const txt = String(buf);
|
|
8466
|
+
errText += txt;
|
|
8467
|
+
const frames = txt.split(/\r/);
|
|
8468
|
+
const cur = (frames[frames.length - 1] || '').trim();
|
|
8469
|
+
const m = /(\d{1,3})%/.exec(cur);
|
|
8470
|
+
const pct = m ? Math.min(99, Number(m[1])) : null;
|
|
8471
|
+
const now = Date.now();
|
|
8472
|
+
if (cur && (pct !== null ? pct !== lastPct : now - lastEmit > 400)) {
|
|
8473
|
+
lastPct = pct != null ? pct : lastPct;
|
|
8474
|
+
lastEmit = now;
|
|
8475
|
+
try {
|
|
8476
|
+
if (win && !win.isDestroyed()) {
|
|
8477
|
+
win.webContents.send('agent:event', { type: 'sb-clone', pct, text: cur.slice(0, 90) });
|
|
8478
|
+
}
|
|
8479
|
+
} catch {}
|
|
8480
|
+
}
|
|
8481
|
+
};
|
|
8482
|
+
let proc;
|
|
8483
|
+
try {
|
|
8484
|
+
proc = spawn('git', ['clone', '--progress', repo.url, dest], { windowsHide: true });
|
|
8485
|
+
} catch (e) {
|
|
8486
|
+
return resolve({ ok: false, error: 'git başlatılamadı: ' + String((e && e.message) || e) });
|
|
8487
|
+
}
|
|
8488
|
+
proc.stdout.on('data', onChunk);
|
|
8489
|
+
proc.stderr.on('data', onChunk);
|
|
8490
|
+
const done = (err) => {
|
|
8491
|
+
if (settled) return;
|
|
8492
|
+
settled = true;
|
|
8493
|
+
try {
|
|
8494
|
+
if (win && !win.isDestroyed()) win.webContents.send('agent:event', { type: 'sb-clone', pct: null, done: !err });
|
|
8495
|
+
} catch {}
|
|
8496
|
+
if (err) {
|
|
8497
|
+
try { fs.rmSync(dest, { recursive: true, force: true }); } catch {}
|
|
8498
|
+
const estr = errText || String((err && err.message) || err);
|
|
8499
|
+
const msg = err && err.code === 'ENOENT'
|
|
8500
|
+
? 'git kurulu değil — https://git-scm.com adresinden kur'
|
|
8501
|
+
: /not found|does not exist|Could not/i.test(estr)
|
|
8502
|
+
? 'repo bulunamadı ya da erişilemedi (gizli repo mu?)'
|
|
8503
|
+
: /Authentication|403|Permission/i.test(estr)
|
|
8504
|
+
? 'erişim reddedildi (gizli repo ya da ağ sorunu)'
|
|
8505
|
+
: (estr.split('\n').filter((l) => l.trim()).pop() || 'klonlama başarısız').slice(0, 200);
|
|
8506
|
+
resolve({ ok: false, error: msg });
|
|
8507
|
+
return;
|
|
8508
|
+
}
|
|
8509
|
+
log.info('main', 'sandbox: klonlandı ' + repo.url + ' → ' + finalName);
|
|
8510
|
+
resolve({ ok: true, name: finalName, folder: dest });
|
|
8511
|
+
};
|
|
8512
|
+
proc.on('error', done);
|
|
8513
|
+
proc.on('close', (code) => done(code === 0 ? null : { code }));
|
|
8514
|
+
/* güvenlik tavanı: 10 dk */
|
|
8515
|
+
setTimeout(() => {
|
|
8516
|
+
if (!settled) {
|
|
8517
|
+
try { proc.kill(); } catch {}
|
|
8518
|
+
done(new Error('zaman aşımı'));
|
|
8519
|
+
}
|
|
8520
|
+
}, 600000);
|
|
8521
|
+
});
|
|
8522
|
+
});
|
|
8523
|
+
|
|
8524
|
+
ipcMain.handle('sandbox:send', async (_e, payload) => {
|
|
8525
|
+
const text = String((payload && payload.msg) || '').trim();
|
|
8526
|
+
const attachments = Array.isArray(payload && payload.attachments) ? payload.attachments : [];
|
|
8527
|
+
const folder = String((payload && payload.folder) || '');
|
|
8528
|
+
if (!folder) return { ok: false, error: 'repo seçilmedi' };
|
|
8529
|
+
if (!folder.startsWith(sandboxRoot() + path.sep)) return { ok: false, error: 'geçersiz klasör' };
|
|
8530
|
+
if (!text && !attachments.length) return { ok: false, error: 'boş mesaj' };
|
|
8531
|
+
if (!engine) return { ok: false, error: 'ajan hazır değil' };
|
|
8532
|
+
if (!engine.publicState().hasModel) return { ok: false, error: 'model yok — Ayarlar → Provider sekmesinden ekle' };
|
|
8533
|
+
const s = sandboxGetSession(folder);
|
|
8534
|
+
s.workspace = folder;
|
|
8535
|
+
s.bcCode = true;
|
|
8536
|
+
s.sbSandbox = true;
|
|
8537
|
+
engine.cache.set(s.id, s);
|
|
8538
|
+
const busy = engine.isBusy(s.id);
|
|
8539
|
+
const q = sbQueuePush(folder, text, attachments);
|
|
8540
|
+
if (busy) return { ok: true, queued: true, count: q.msgs.length, sessionId: s.id };
|
|
8541
|
+
clearTimeout(q.timer);
|
|
8542
|
+
q.timer = setTimeout(() => { try { sbFlush(folder); } catch {} }, SB_DEBOUNCE_MS);
|
|
8543
|
+
return { ok: true, sessionId: s.id, pending: true };
|
|
8544
|
+
});
|
|
8545
|
+
|
|
8546
|
+
ipcMain.handle('sandbox:stop', async (_e, payload) => {
|
|
8547
|
+
const folder = String((payload && payload.folder) || '');
|
|
8548
|
+
sbLoadMap();
|
|
8549
|
+
const sid = sbSessions.get(folder);
|
|
8550
|
+
const q = sbQueue.get(folder);
|
|
8551
|
+
if (q) {
|
|
8552
|
+
clearTimeout(q.timer);
|
|
8553
|
+
sbQueue.delete(folder);
|
|
8554
|
+
}
|
|
8555
|
+
const wasBusy = sid ? engine.isBusy(sid) : false;
|
|
8556
|
+
let r = false;
|
|
8557
|
+
if (wasBusy) {
|
|
8558
|
+
try { r = engine.interrupt(sid, 'kullanıcı Sandbox panelinden ■ ile durdurdu'); } catch {}
|
|
8559
|
+
}
|
|
8560
|
+
return { ok: true, wasBusy, interrupted: r };
|
|
8561
|
+
});
|
|
8562
|
+
|
|
8563
|
+
ipcMain.handle('sandbox:new', async (_e, payload) => {
|
|
8564
|
+
const folder = String((payload && payload.folder) || '');
|
|
8565
|
+
sbLoadMap();
|
|
8566
|
+
const sid = sbSessions.get(folder);
|
|
8567
|
+
if (sid && engine.isBusy(sid)) return { ok: false, error: 'mesaj sürüyor — önce ■ ile durdur' };
|
|
8568
|
+
const q = sbQueue.get(folder);
|
|
8569
|
+
if (q) {
|
|
8570
|
+
clearTimeout(q.timer);
|
|
8571
|
+
sbQueue.delete(folder);
|
|
8572
|
+
}
|
|
8573
|
+
if (sid) {
|
|
8574
|
+
try { engine.deleteSession(sid); } catch {}
|
|
8575
|
+
sbSessions.delete(folder);
|
|
8576
|
+
sbSaveMap();
|
|
8577
|
+
}
|
|
8578
|
+
return { ok: true };
|
|
8579
|
+
});
|
|
8580
|
+
|
|
8581
|
+
/* repo klasörünü diskten sil (oturum kaydıyla birlikte temizlenir) */
|
|
8582
|
+
ipcMain.handle('sandbox:remove', async (_e, payload) => {
|
|
8583
|
+
const folder = String((payload && payload.folder) || '');
|
|
8584
|
+
if (!folder.startsWith(sandboxRoot() + path.sep)) return { ok: false, error: 'geçersiz klasör' };
|
|
8585
|
+
sbLoadMap();
|
|
8586
|
+
const sid = sbSessions.get(folder);
|
|
8587
|
+
if (sid && engine.isBusy(sid)) return { ok: false, error: 'repo çalışıyor — önce ■ ile durdur' };
|
|
8588
|
+
try { fs.rmSync(folder, { recursive: true, force: true }); } catch (e) {
|
|
8589
|
+
return { ok: false, error: String((e && e.message) || e) };
|
|
8590
|
+
}
|
|
8591
|
+
if (sid) {
|
|
8592
|
+
try { engine.deleteSession(sid); } catch {}
|
|
8593
|
+
sbSessions.delete(folder);
|
|
8594
|
+
sbSaveMap();
|
|
8595
|
+
}
|
|
8596
|
+
return { ok: true };
|
|
8597
|
+
});
|
|
8598
|
+
|
|
8599
|
+
ipcMain.handle('sandbox:reveal', async (_e, payload) => {
|
|
8600
|
+
const folder = String((payload && payload.folder) || '');
|
|
8601
|
+
if (!folder.startsWith(sandboxRoot() + path.sep)) return { ok: false, error: 'geçersiz klasör' };
|
|
8602
|
+
try { require('electron').shell.openPath(folder); return { ok: true }; } catch (e) {
|
|
8603
|
+
return { ok: false, error: String((e && e.message) || e) };
|
|
8604
|
+
}
|
|
8605
|
+
});
|
|
8606
|
+
|
|
8607
|
+
/* seçili reponun oturum geçmişini panele döndürür (yeni oturum yoksa oluşturur) */
|
|
8608
|
+
ipcMain.handle('sandbox:open', async (_e, payload) => {
|
|
8609
|
+
const folder = String((payload && payload.folder) || '');
|
|
8610
|
+
if (!folder.startsWith(sandboxRoot() + path.sep)) return { ok: false, error: 'geçersiz klasör' };
|
|
8611
|
+
if (!engine) return { ok: false, error: 'ajan hazır değil' };
|
|
8612
|
+
const s = sandboxGetSession(folder);
|
|
8613
|
+
s.workspace = folder;
|
|
8614
|
+
engine.cache.set(s.id, s);
|
|
8615
|
+
const msgs = [];
|
|
8616
|
+
for (const m of s.messages || []) {
|
|
8617
|
+
if (m.tool_calls) continue;
|
|
8618
|
+
const txt = bcMsgText(m);
|
|
8619
|
+
if (!txt) continue;
|
|
8620
|
+
msgs.push({ role: m.role === 'assistant' ? 'assistant' : 'user', text: txt.slice(0, 4000) });
|
|
8621
|
+
}
|
|
8622
|
+
return {
|
|
8623
|
+
ok: true,
|
|
8624
|
+
sessionId: s.id,
|
|
8625
|
+
busy: !!engine.isBusy(s.id),
|
|
8626
|
+
messages: msgs.slice(-200),
|
|
8627
|
+
};
|
|
8628
|
+
});
|
|
8629
|
+
|
|
8630
|
+
/* Sandbox dosya konsolu: seçili repo klasörünün ağacı (filePanel yeniden kullanılır) */
|
|
8631
|
+
ipcMain.handle('sandbox:tree', (_e, payload) => {
|
|
8632
|
+
const folder = String((payload && payload.folder) || '');
|
|
8633
|
+
if (!folder.startsWith(sandboxRoot() + path.sep)) return { ok: false, error: 'geçersiz klasör' };
|
|
8634
|
+
const p = path.resolve(folder, String((payload && payload.rel) || ''));
|
|
8635
|
+
if (p !== folder && !p.startsWith(folder + path.sep)) return { ok: false, error: 'geçersiz yol' };
|
|
8636
|
+
try {
|
|
8637
|
+
const entries = fs.readdirSync(p, { withFileTypes: true })
|
|
8638
|
+
.filter((e) => e.name !== 'node_modules' && e.name !== '.git')
|
|
8639
|
+
.map((e) => {
|
|
8640
|
+
let size = 0;
|
|
8641
|
+
if (!e.isDirectory()) {
|
|
8642
|
+
try { size = fs.statSync(path.join(p, e.name)).size; } catch {}
|
|
8643
|
+
}
|
|
8644
|
+
return { name: e.name, dir: e.isDirectory(), size };
|
|
8645
|
+
})
|
|
8646
|
+
.sort((a, b) => (a.dir === b.dir ? a.name.localeCompare(b.name) : a.dir ? -1 : 1));
|
|
8647
|
+
return { ok: true, workspace: folder, entries };
|
|
8648
|
+
} catch (e) {
|
|
8649
|
+
return { ok: false, error: String((e && e.message) || e) };
|
|
8650
|
+
}
|
|
8651
|
+
});
|
|
8652
|
+
|
|
8653
|
+
/* Sandbox ÇALIŞTIRICI: repo klasöründe uzun süreli komut (npm start/dev vb.)
|
|
8654
|
+
YÖNETİLEN SÜREÇ — çıktı satır satır panele akar; ■ ile ağaç-kullanı durdurulur.
|
|
8655
|
+
Dev server adresi çıktıdan yakalanır → 'Tarayıcıda aç' aktifleşir.
|
|
8656
|
+
Aynı fonksiyon engine.sbRunHook olarak da bağlanır — ajanın panel_run
|
|
8657
|
+
aracı turu KİLİTLEMEDEN süreç başlatır (Kur/Başlat butonları bunu kullanır). */
|
|
8658
|
+
const sbRunProc = { proc: null, folder: '', url: '' };
|
|
8659
|
+
|
|
8660
|
+
function sbRunEmit(data) {
|
|
8661
|
+
try {
|
|
8662
|
+
if (win && !win.isDestroyed()) win.webContents.send('agent:event', { type: 'sb-run', data: String(data || '') });
|
|
8663
|
+
} catch {}
|
|
8664
|
+
}
|
|
8665
|
+
|
|
8666
|
+
function sbRunStartManaged(folder, cmd) {
|
|
8667
|
+
folder = String(folder || '');
|
|
8668
|
+
cmd = String(cmd || '').trim();
|
|
8669
|
+
if (!folder.startsWith(sandboxRoot() + path.sep)) return { ok: false, error: 'geçersiz klasör' };
|
|
8670
|
+
if (!cmd) return { ok: false, error: 'komut boş' };
|
|
8671
|
+
if (sbRunProc.proc) return { ok: false, error: 'ÇALIŞTIR panelinde zaten bir süreç çalışıyor — önce ■ ile durdur' };
|
|
8672
|
+
const { spawn } = require('child_process');
|
|
8673
|
+
try {
|
|
8674
|
+
const proc = spawn('cmd.exe', ['/d', '/s', '/c', cmd], {
|
|
8675
|
+
cwd: folder,
|
|
8676
|
+
windowsHide: true,
|
|
8677
|
+
env: { ...process.env, FORCE_COLOR: '0', NO_COLOR: '1' },
|
|
8678
|
+
});
|
|
8679
|
+
sbRunProc.proc = proc;
|
|
8680
|
+
sbRunProc.folder = folder;
|
|
8681
|
+
sbRunProc.url = '';
|
|
8682
|
+
const onLine = (buf) => {
|
|
8683
|
+
for (const piece of String(buf).split(/\r?\n/)) {
|
|
8684
|
+
if (!piece.trim()) continue;
|
|
8685
|
+
sbRunEmit(piece);
|
|
8686
|
+
/* dev server adresi yakala (sadece localhost) */
|
|
8687
|
+
const m = /https?:\/\/(?:localhost|127\.0\.0\.1)(?::\d{2,5})?[^\s'"<>]*/i.exec(piece);
|
|
8688
|
+
if (m && !sbRunProc.url) {
|
|
8689
|
+
sbRunProc.url = m[0].replace(/[)\].,;:'"]+$/, '');
|
|
8690
|
+
try {
|
|
8691
|
+
if (win && !win.isDestroyed()) win.webContents.send('agent:event', { type: 'sb-run-url', url: sbRunProc.url });
|
|
8692
|
+
} catch {}
|
|
8693
|
+
}
|
|
8694
|
+
}
|
|
8695
|
+
};
|
|
8696
|
+
proc.stdout.on('data', onLine);
|
|
8697
|
+
proc.stderr.on('data', onLine);
|
|
8698
|
+
proc.on('close', (code) => {
|
|
8699
|
+
sbRunEmit('[süreç bitti' + (code != null ? ' — kod ' + code : '') + ']');
|
|
8700
|
+
sbRunProc.proc = null;
|
|
8701
|
+
try {
|
|
8702
|
+
if (win && !win.isDestroyed()) win.webContents.send('agent:event', { type: 'sb-run-end' });
|
|
8703
|
+
} catch {}
|
|
8704
|
+
});
|
|
8705
|
+
sbRunEmit('▶ ' + cmd + ' (klasör: ' + path.basename(folder) + ')');
|
|
8706
|
+
return {
|
|
8707
|
+
ok: true,
|
|
8708
|
+
note: 'süreç ÇALIŞTIR panelinde başlatıldı — çıktı orada canlı akar; durdurmak için paneldeki ■. Kullanıcıya adres varsa bildir.',
|
|
8709
|
+
};
|
|
8710
|
+
} catch (e) {
|
|
8711
|
+
sbRunProc.proc = null;
|
|
8712
|
+
return { ok: false, error: String((e && e.message) || e) };
|
|
8713
|
+
}
|
|
8714
|
+
}
|
|
8715
|
+
|
|
8716
|
+
ipcMain.handle('sandbox:run', async (_e, payload) => {
|
|
8717
|
+
const folder = String((payload && payload.folder) || '');
|
|
8718
|
+
const cmd = String((payload && payload.cmd) || '').trim();
|
|
8719
|
+
return sbRunStartManaged(folder, cmd);
|
|
8720
|
+
});
|
|
8721
|
+
|
|
8722
|
+
ipcMain.handle('sandbox:runstop', async () => {
|
|
8723
|
+
const p = sbRunProc.proc;
|
|
8724
|
+
if (!p) return { ok: true, wasRunning: false };
|
|
8725
|
+
sbRunProc.proc = null;
|
|
8726
|
+
try {
|
|
8727
|
+
/* cmd.exe /c zinciriyle çocuklar (node/vite) geride kalmasın — ağaç kesimi */
|
|
8728
|
+
const { exec } = require('child_process');
|
|
8729
|
+
if (process.platform === 'win32') exec('taskkill /pid ' + p.pid + ' /T /F', { windowsHide: true });
|
|
8730
|
+
else p.kill('SIGTERM');
|
|
8731
|
+
} catch {}
|
|
8732
|
+
return { ok: true, wasRunning: true };
|
|
8733
|
+
});
|
|
8734
|
+
|
|
8735
|
+
ipcMain.handle('sandbox:openurl', async (_e, url) => {
|
|
8736
|
+
const u = String(url || '').trim();
|
|
8737
|
+
if (!/^https?:\/\/(?:localhost|127\.0\.0\.1)(?::\d{2,5})?(?:\/|$)/i.test(u)) return { ok: false, error: 'yalnız localhost adresleri açılabilir' };
|
|
8738
|
+
try {
|
|
8739
|
+
setBrowserOpen(true, true);
|
|
8740
|
+
browser.view.webContents.loadURL(u).catch(() => {});
|
|
8741
|
+
return { ok: true, url: u };
|
|
8742
|
+
} catch (e) {
|
|
8743
|
+
return { ok: false, error: String((e && e.message) || e) };
|
|
8744
|
+
}
|
|
8745
|
+
});
|
|
8746
|
+
|
|
8229
8747
|
/* Sağ tık menüsü: HTML dosyasını dahili tarayıcıda GÖRÜNÜR aç */
|
|
8230
8748
|
/* ---------- BC DAHİLİ STATİK SUNUCU ----------
|
|
8231
8749
|
Beast Code çıktısı ASLA file:// ile açılmaz: ES modülleri, fetch, ServiceWorker
|
|
@@ -8236,6 +8754,23 @@ const bcStatic = { server: null, root: '', base: '' };
|
|
|
8236
8754
|
/* MOBİL PROJE önizleme: npm run web (expo start --web) süreci + canlı adres */
|
|
8237
8755
|
const bcMobile = { proc: null, url: '', root: '' };
|
|
8238
8756
|
|
|
8757
|
+
/* ---------- EXPO GO QR (yalnız Beast Code) ----------
|
|
8758
|
+
Ajan expo start çıktısında exp://<LAN-IP>:<port> yazınca QR'e çevrilir ve
|
|
8759
|
+
renderer'a { type:'bc-exp-qr', qr, url } düşer — telefonda Expo Go okutulur. */
|
|
8760
|
+
let bcLastExpUrl = '';
|
|
8761
|
+
let bcLastExpQr = '';
|
|
8762
|
+
|
|
8763
|
+
function bcExpQrMake(url) {
|
|
8764
|
+
if (!url || url === bcLastExpUrl) return Promise.resolve();
|
|
8765
|
+
bcLastExpUrl = url;
|
|
8766
|
+
return QRCode.toDataURL(url, { width: 240, margin: 1, errorCorrectionLevel: 'M' }).then((qr) => {
|
|
8767
|
+
bcLastExpQr = qr;
|
|
8768
|
+
if (win && !win.isDestroyed()) win.webContents.send('agent:event', { type: 'bc-exp-qr', qr, url });
|
|
8769
|
+
});
|
|
8770
|
+
}
|
|
8771
|
+
|
|
8772
|
+
ipcMain.handle('ide:expqr', () => ({ ok: true, qr: bcLastExpQr, url: bcLastExpUrl }));
|
|
8773
|
+
|
|
8239
8774
|
/* workspace bir MOBİL UYGULAMA projesi mi? (expo / react-native / app.json) */
|
|
8240
8775
|
function ideMobileProject(root) {
|
|
8241
8776
|
try {
|
|
@@ -8260,6 +8795,49 @@ function ideMobileProject(root) {
|
|
|
8260
8795
|
}
|
|
8261
8796
|
}
|
|
8262
8797
|
|
|
8798
|
+
/* Beast Code PROJE TİPİ algılama: preview butonu ajana hangi talimatı vereceğini
|
|
8799
|
+
bununla seçer. kind: expo | react-native | web | static | unknown */
|
|
8800
|
+
function ideProjectInfo(root) {
|
|
8801
|
+
const out = { kind: 'unknown', name: '', cmd: '' };
|
|
8802
|
+
try {
|
|
8803
|
+
const pkgPath = path.join(root, 'package.json');
|
|
8804
|
+
let pkg = null;
|
|
8805
|
+
try { if (fs.existsSync(pkgPath)) pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); } catch {}
|
|
8806
|
+
if (!pkg) {
|
|
8807
|
+
out.kind = fs.existsSync(path.join(root, 'index.html')) ? 'static' : 'unknown';
|
|
8808
|
+
return out;
|
|
8809
|
+
}
|
|
8810
|
+
out.name = String(pkg.name || '');
|
|
8811
|
+
const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
|
|
8812
|
+
const scripts = pkg.scripts || {};
|
|
8813
|
+
const hasAppJson = fs.existsSync(path.join(root, 'app.json')) || fs.existsSync(path.join(root, 'app.config.js'));
|
|
8814
|
+
if (deps.expo || (hasAppJson && (deps['react-native'] || deps['react-dom']))) {
|
|
8815
|
+
out.kind = 'expo';
|
|
8816
|
+
out.cmd = scripts.web ? 'npm run web' : deps.expo ? 'npx expo start --web' : String(scripts.start || '');
|
|
8817
|
+
} else if (deps['react-native']) {
|
|
8818
|
+
out.kind = 'react-native';
|
|
8819
|
+
out.cmd = String(scripts.start || scripts.ios || '') || 'npx react-native start';
|
|
8820
|
+
} else if (deps.next) {
|
|
8821
|
+
out.kind = 'web';
|
|
8822
|
+
out.cmd = String(scripts.dev || scripts.start || '') || 'npx next dev';
|
|
8823
|
+
} else if (deps.vite || deps['react-scripts'] || deps['@angular/core'] || deps['vue']) {
|
|
8824
|
+
out.kind = 'web';
|
|
8825
|
+
out.cmd = String(scripts.dev || scripts.start || scripts.serve || '');
|
|
8826
|
+
} else if (scripts.dev) {
|
|
8827
|
+
out.kind = 'web';
|
|
8828
|
+
out.cmd = 'npm run dev';
|
|
8829
|
+
} else if (scripts.start) {
|
|
8830
|
+
out.kind = 'web';
|
|
8831
|
+
out.cmd = 'npm start';
|
|
8832
|
+
} else {
|
|
8833
|
+
out.kind = fs.existsSync(path.join(root, 'index.html')) ? 'static' : 'unknown';
|
|
8834
|
+
}
|
|
8835
|
+
} catch {}
|
|
8836
|
+
return out;
|
|
8837
|
+
}
|
|
8838
|
+
|
|
8839
|
+
ipcMain.handle('ide:projinfo', () => ({ ok: true, ...ideProjectInfo(ideRoot()) }));
|
|
8840
|
+
|
|
8263
8841
|
function killMobilePreview() {
|
|
8264
8842
|
const p = bcMobile.proc;
|
|
8265
8843
|
if (p) {
|
|
@@ -8510,6 +9088,7 @@ ipcMain.handle('custom:set', (_e, list) => {
|
|
|
8510
9088
|
settings.customProviders = Array.isArray(list) ? list : [];
|
|
8511
9089
|
saveSettings();
|
|
8512
9090
|
engine.setCustomProviders(settings.customProviders);
|
|
9091
|
+
headroom.syncProviders(); /* headroom açıksa proxy setini yeni listeye uydur */
|
|
8513
9092
|
return engine.publicState();
|
|
8514
9093
|
});
|
|
8515
9094
|
|
|
@@ -8641,8 +9220,8 @@ async function fetchZenFreeModels() {
|
|
|
8641
9220
|
if (free.length) return free;
|
|
8642
9221
|
}
|
|
8643
9222
|
} catch {}
|
|
8644
|
-
/* API cevap vermezse bilinen free liste */
|
|
8645
|
-
return ['big-pickle', 'deepseek-v4-flash-free', 'mimo-v2.5-free', '
|
|
9223
|
+
/* API cevap vermezse bilinen free liste (09/2026 güncel — eski adlar düşürüldü) */
|
|
9224
|
+
return ['big-pickle', 'deepseek-v4-flash-free', 'mimo-v2.5-free', 'muse-spark-1.3-contributor-free', 'muse-spark-1.2-contributor-free', 'ling-3.0-flash-fin-free', 'nemotron-3-ultra-free', 'nemotron-3.5-lightning-free'];
|
|
8646
9225
|
}
|
|
8647
9226
|
|
|
8648
9227
|
function upsertZenEnv(token) {
|