beast-agent 2.11.0 → 2.12.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 +24 -0
- package/src/agent/tools.js +15 -0
- package/src/main.js +461 -7
- package/src/preload.js +13 -0
- package/src/renderer/i18n.js +2 -0
- package/src/renderer/index.html +50 -3
- package/src/renderer/renderer.js +551 -9
- package/src/renderer/style.css +269 -0
package/package.json
CHANGED
package/src/agent/engine.js
CHANGED
|
@@ -1480,6 +1480,9 @@ class Engine {
|
|
|
1480
1480
|
'Sen BEAST CODE\u2019sun — IDE panelinde çalışan, OpenCode disiplinli hızlı bir kodlama ajanı. VS Code gibi çevik ol.\n' +
|
|
1481
1481
|
'GENEL AMAÇLI KODLAMA AJANISIN — tek bir proje türüne sınırlı değilsin: web, backend/API, mobil, masaüstü, CLI, kütüphane, script, oyun, veri, DevOps, gömülü… İstenen neyse o. İstenmeyen şeyi (ör. site değilken site) kendiliğinden ÜRETME.\n' +
|
|
1482
1482
|
`Çalışma klasörü: ${(session && session.workspace) || this.workspace}\n` +
|
|
1483
|
+
((session && session.sbSandbox)
|
|
1484
|
+
? 'BEAST SANDBOX MODUNDASIN: GitHub\u2019dan indirilmiş AÇIK KAYNAK bir repo klasöründesin (Beast-Sandbox). Bu kodun sahibi değilsin — hazır bir dış proje; repo sahibinin konvansiyonlarına uy, gereksiz yeniden biçimlendirme yapma. İşe README + paket bildirim dosyasıyla başla (list_dir/glob); bağımlılıklar kurulu değilse önce kurulum komutunu çalıştır (npm install vb.). UZUN SÜRELİ sunucu/süreç (npm start, npm run dev, python app.py vb.) run_command ile ASLA başlatma — tur kilitlenir; kullanıcı çalıştırmak isterse panel_run aracıyla ÇALIŞTIR paneline bırakın (çıktı orada canlı akar, ■ ile durur) ve adresi kullanıcıya bildir. panel_run yalnız BİR süreç çalıştırır.\n'
|
|
1485
|
+
: '') +
|
|
1483
1486
|
`Yerel zaman: ${localDate} ${localTime}\n` +
|
|
1484
1487
|
`${modeBlock}\n` +
|
|
1485
1488
|
'STACK TESPİTİ: işe başlarken workspace\u2019i tanı — package.json, go.mod, Cargo.toml, requirements.txt/pyproject.toml, pom.xml/build.gradle, *.csproj, pubspec.yaml, Gemfile, composer.json vb. işaret dosyalarına bak (list_dir + glob). Dili, framework\u2019ü, konvansiyonları ve build/test/run komutlarını PROJE belirler; her işte aynı teknolojiye itme, mevcut stack\u2019e uy.\n' +
|
|
@@ -3106,6 +3109,11 @@ class Engine {
|
|
|
3106
3109
|
if (!session || !session.bcCode) toolsList = await mcp.mergeTools(toolsList);
|
|
3107
3110
|
/* Beast Apps: kurulu app'lerin araçları (app__<id>__<tool>) modele açılır */
|
|
3108
3111
|
toolsList = apps.mergeTools(toolsList);
|
|
3112
|
+
/* panel_run yalnız SANDBOX oturumlarında görünsün — diğer panellerde
|
|
3113
|
+
modelin alet çantasında olmasın (hook olmadan çalışmaz) */
|
|
3114
|
+
if (!(session && session.sbSandbox)) {
|
|
3115
|
+
toolsList = toolsList.filter((t) => !(t && t.function && t.function.name === 'panel_run'));
|
|
3116
|
+
}
|
|
3109
3117
|
/* opencode agent.ts port: özel ajan tanımı — prompt/model/araç/steps */
|
|
3110
3118
|
const adef = this._agentDefFor(session, !!session.bgJob);
|
|
3111
3119
|
/* Beast Code: todo_write açıklaması "3+ adım" kısıtı içerir ve model küçük
|
|
@@ -4229,6 +4237,22 @@ const skills = require('./skills');
|
|
|
4229
4237
|
}
|
|
4230
4238
|
/* GERİ ALMA GÜNLÜĞÜ: dosya yazımından ÖNCE eski içerik kayda geçer */
|
|
4231
4239
|
this._journalBefore(sessionId, name, args);
|
|
4240
|
+
/* PANEL RUN: uzun süreli süreç ÇALIŞTIR paneline devredilir — tur
|
|
4241
|
+
BLOKLAMAZ (main'deki yönetilen süreç koşucusuna köprü). */
|
|
4242
|
+
if (name === 'panel_run') {
|
|
4243
|
+
const cmd = String((args && args.command) || '').trim();
|
|
4244
|
+
if (!cmd) return JSON.stringify({ ok: false, error: 'komut boş' });
|
|
4245
|
+
if (typeof this.sbRunHook !== 'function') {
|
|
4246
|
+
return JSON.stringify({ ok: false, error: 'panel koşucusu yok — bu araç yalnız Sandbox panelinde çalışır' });
|
|
4247
|
+
}
|
|
4248
|
+
let r;
|
|
4249
|
+
try {
|
|
4250
|
+
r = this.sbRunHook(this._sessionWorkspace(sessionId), cmd);
|
|
4251
|
+
} catch (e) {
|
|
4252
|
+
r = { ok: false, error: String((e && e.message) || e) };
|
|
4253
|
+
}
|
|
4254
|
+
return JSON.stringify(r);
|
|
4255
|
+
}
|
|
4232
4256
|
/* MCP: dış server araçları (mcp__<server>__<tool>) — tools/call'a köprülenir */
|
|
4233
4257
|
if (String(name).startsWith('mcp__')) {
|
|
4234
4258
|
return JSON.stringify(await mcp.call(name, args, signal));
|
package/src/agent/tools.js
CHANGED
|
@@ -1488,6 +1488,21 @@ const definitions = [
|
|
|
1488
1488
|
},
|
|
1489
1489
|
},
|
|
1490
1490
|
},
|
|
1491
|
+
{
|
|
1492
|
+
type: 'function',
|
|
1493
|
+
function: {
|
|
1494
|
+
name: 'panel_run',
|
|
1495
|
+
description:
|
|
1496
|
+
'Starts a LONG-RUNNING process (dev server, npm start, npm run dev, python app.py, cargo run…) in the managed RUN PANEL instead of blocking this turn. Returns immediately after spawn; output streams live to the panel, the user can stop it with ■ there. Use this when the user asks to RUN/START the project or a server. NEVER use for short one-shot commands (build/test/lint) — use run_command for those. Only one managed process can run at a time.',
|
|
1497
|
+
parameters: {
|
|
1498
|
+
type: 'object',
|
|
1499
|
+
properties: {
|
|
1500
|
+
command: { type: 'string', description: 'Shell command to start, e.g. "npm start" or "npm run dev"' },
|
|
1501
|
+
},
|
|
1502
|
+
required: ['command'],
|
|
1503
|
+
},
|
|
1504
|
+
},
|
|
1505
|
+
},
|
|
1491
1506
|
{
|
|
1492
1507
|
type: 'function',
|
|
1493
1508
|
function: {
|
package/src/main.js
CHANGED
|
@@ -3103,6 +3103,7 @@ function reloadBackend() {
|
|
|
3103
3103
|
flushDesktopOnDone(ev); /* biriken desktop mesajlarını sıraya bas */
|
|
3104
3104
|
bcFlushOnDone(ev); /* Beast Code kuyruğunu iş bitiminde boşalt */
|
|
3105
3105
|
stFlushOnDone(ev); /* Beast Studio kuyruğunu iş bitiminde boşalt */
|
|
3106
|
+
sbFlushOnDone(ev); /* Sandbox kuyruğunu iş bitiminde boşalt */
|
|
3106
3107
|
/* BC canlı önizleme: ajan bir dev server başlattıysa adresi yakala —
|
|
3107
3108
|
preview butonu ve otomatik açılış DAİMA bu sunucuyu öncelikli kullanır */
|
|
3108
3109
|
if (ev.type === 'bc-preview' && /^https?:\/\/(?:localhost|127\.0\.0\.1)(?::\d{2,5})?/i.test(String(ev.url || ''))) {
|
|
@@ -3284,6 +3285,9 @@ function reloadBackend() {
|
|
|
3284
3285
|
}
|
|
3285
3286
|
},
|
|
3286
3287
|
});
|
|
3288
|
+
/* PANEL RUN köprüsü: ajanın panel_run aracı → ÇALIŞTIR panelindeki yönetilen
|
|
3289
|
+
süreç koşucusu (sandbox:run IPC ile aynı makine). */
|
|
3290
|
+
engine.sbRunHook = sbRunStartManaged;
|
|
3287
3291
|
return engine.publicState();
|
|
3288
3292
|
}
|
|
3289
3293
|
|
|
@@ -7459,9 +7463,10 @@ ipcMain.handle('beastcode:send', async (_e, payload) => {
|
|
|
7459
7463
|
return { ok: true, sessionId: s.id, mode: s.bcMode };
|
|
7460
7464
|
}
|
|
7461
7465
|
if (busy) {
|
|
7462
|
-
/*
|
|
7463
|
-
|
|
7464
|
-
|
|
7466
|
+
/* OPENCODE STEER: koşan tur varken mesaj kuyrukta BEKLEMEZ — engine.send
|
|
7467
|
+
konuşmaya ekler, koşan tur sonraki istekte görür (ana chat ile aynı) */
|
|
7468
|
+
engine.send(s.id, attachments.length ? { text, attachments } : text, { userAction: true });
|
|
7469
|
+
return { ok: true, sessionId: s.id, steered: true };
|
|
7465
7470
|
}
|
|
7466
7471
|
/* boşta: kısa pencere — hızlı ard arda mesajlar tek işte birleşir */
|
|
7467
7472
|
const q = bcQueuePush(ws, text, attachments);
|
|
@@ -7745,8 +7750,9 @@ ipcMain.handle('studio:send', async (_e, payload) => {
|
|
|
7745
7750
|
return { ok: true, sessionId: s.id, mode: s.bcMode };
|
|
7746
7751
|
}
|
|
7747
7752
|
if (busy) {
|
|
7748
|
-
|
|
7749
|
-
|
|
7753
|
+
/* OPENCODE STEER: koşan tur varken mesaj kuyrukta beklemez — konuşmaya eklenir */
|
|
7754
|
+
engine.send(s.id, attachments.length ? { text, attachments } : text, { userAction: true });
|
|
7755
|
+
return { ok: true, sessionId: s.id, steered: true };
|
|
7750
7756
|
}
|
|
7751
7757
|
const q = stQueuePush(ws, text, attachments);
|
|
7752
7758
|
clearTimeout(q.timer);
|
|
@@ -8226,6 +8232,454 @@ ipcMain.handle('studio:delete', async (_e, rel) => {
|
|
|
8226
8232
|
}
|
|
8227
8233
|
});
|
|
8228
8234
|
|
|
8235
|
+
/* Sağ tık menüsü: HTML dosyasını dahili tarayıcıda GÖRÜNÜR aç */
|
|
8236
|
+
/* ---------- BEAST SANDBOX: GitHub repolarını indir & içinde çalış ----------
|
|
8237
|
+
Her repo KENDİ klasöründe (~/Beast-Sandbox/<ad>) ve KENDİ gizli oturumunda
|
|
8238
|
+
koşar (klasör bazlı, bcCode disiplinli — Beast Code motorunun aynısı; ajanın
|
|
8239
|
+
run_command/read/write araçları repo klasöründe çalışır). Ana sohbet VE
|
|
8240
|
+
Code/Studio panellerinden tamamen ayrıdır; ana chat solda yerinde kalır. */
|
|
8241
|
+
const sbSessions = new Map(); /* klasör → sessionId */
|
|
8242
|
+
const SB_DEBOUNCE_MS = 900;
|
|
8243
|
+
const sbQueue = new Map(); /* klasör → { timer, msgs[] } */
|
|
8244
|
+
|
|
8245
|
+
function sandboxRoot() {
|
|
8246
|
+
const dir = path.join(app.getPath('home'), 'Beast-Sandbox');
|
|
8247
|
+
try { fs.mkdirSync(dir, { recursive: true }); } catch {}
|
|
8248
|
+
return dir;
|
|
8249
|
+
}
|
|
8250
|
+
|
|
8251
|
+
function sbSessionFile() {
|
|
8252
|
+
return path.join(beastDir(), 'sandbox-sessions.json');
|
|
8253
|
+
}
|
|
8254
|
+
|
|
8255
|
+
let sbMapLoaded = false;
|
|
8256
|
+
function sbLoadMap() {
|
|
8257
|
+
if (sbMapLoaded) return;
|
|
8258
|
+
sbMapLoaded = true;
|
|
8259
|
+
try {
|
|
8260
|
+
const raw = JSON.parse(fs.readFileSync(sbSessionFile(), 'utf8'));
|
|
8261
|
+
for (const [k, v] of Object.entries(raw || {})) {
|
|
8262
|
+
if (k && typeof v === 'string') sbSessions.set(k, v);
|
|
8263
|
+
}
|
|
8264
|
+
} catch {}
|
|
8265
|
+
}
|
|
8266
|
+
|
|
8267
|
+
function sbSaveMap() {
|
|
8268
|
+
try {
|
|
8269
|
+
fs.writeFileSync(sbSessionFile(), JSON.stringify(Object.fromEntries(sbSessions), null, 2));
|
|
8270
|
+
} catch {}
|
|
8271
|
+
}
|
|
8272
|
+
|
|
8273
|
+
function sandboxGetSession(folder) {
|
|
8274
|
+
sbLoadMap();
|
|
8275
|
+
let sid = sbSessions.get(folder);
|
|
8276
|
+
if (sid) {
|
|
8277
|
+
try {
|
|
8278
|
+
const s = engine.cache.get(sid) || engine._load(sid);
|
|
8279
|
+
if (s) return s;
|
|
8280
|
+
} catch {}
|
|
8281
|
+
sbSessions.delete(folder);
|
|
8282
|
+
}
|
|
8283
|
+
const s = engine._load(engine.createSession().id);
|
|
8284
|
+
s.messages = s.messages || [];
|
|
8285
|
+
s.bgTitle = 'Beast Sandbox'; /* _view.isBg → sohbet geçmişinde gizli */
|
|
8286
|
+
s.bcCode = true; /* todo disiplini + hızlı iş kapanışı (engine) */
|
|
8287
|
+
s.sbSandbox = true; /* engine: SANDBOX sistem bloğu — ajan açık kaynak repoda olduğunu bilir */
|
|
8288
|
+
try {
|
|
8289
|
+
fs.appendFileSync(
|
|
8290
|
+
engine._file(s.id),
|
|
8291
|
+
JSON.stringify({ t: 'meta2', bgOf: '', title: 'Beast Sandbox', at: new Date().toISOString() }) + '\n'
|
|
8292
|
+
);
|
|
8293
|
+
} catch {}
|
|
8294
|
+
bcMarkWs(s, folder); /* klasör bağı oturum dosyasına yazılır */
|
|
8295
|
+
engine.cache.set(s.id, s);
|
|
8296
|
+
sbSessions.set(folder, s.id);
|
|
8297
|
+
sbSaveMap();
|
|
8298
|
+
return s;
|
|
8299
|
+
}
|
|
8300
|
+
|
|
8301
|
+
function sbQueuePush(ws, text, attachments) {
|
|
8302
|
+
let q = sbQueue.get(ws);
|
|
8303
|
+
if (!q) {
|
|
8304
|
+
q = { timer: null, msgs: [] };
|
|
8305
|
+
sbQueue.set(ws, q);
|
|
8306
|
+
}
|
|
8307
|
+
q.msgs.push({
|
|
8308
|
+
text,
|
|
8309
|
+
attachments: Array.isArray(attachments) && attachments.length ? attachments : undefined,
|
|
8310
|
+
});
|
|
8311
|
+
return q;
|
|
8312
|
+
}
|
|
8313
|
+
|
|
8314
|
+
function sbFlush(folder) {
|
|
8315
|
+
const q = sbQueue.get(folder);
|
|
8316
|
+
if (!q || !q.msgs.length) return;
|
|
8317
|
+
const s = sandboxGetSession(folder);
|
|
8318
|
+
if (engine.isBusy(s.id)) return; /* steer sistemine rağmen kuyruğu koru */
|
|
8319
|
+
let merged = '';
|
|
8320
|
+
let mergedAtts = null;
|
|
8321
|
+
for (const m of q.msgs) {
|
|
8322
|
+
if (m.text) merged += (merged ? '\n' : '') + m.text;
|
|
8323
|
+
if (!mergedAtts && Array.isArray(m.attachments) && m.attachments.length) mergedAtts = m.attachments;
|
|
8324
|
+
}
|
|
8325
|
+
if (!merged.trim() && !mergedAtts) {
|
|
8326
|
+
sbQueue.delete(folder);
|
|
8327
|
+
clearTimeout(q.timer);
|
|
8328
|
+
return;
|
|
8329
|
+
}
|
|
8330
|
+
s.workspace = folder;
|
|
8331
|
+
s.bcCode = true;
|
|
8332
|
+
s.sbSandbox = true;
|
|
8333
|
+
engine.cache.set(s.id, s);
|
|
8334
|
+
const payload = mergedAtts ? { text: merged, attachments: mergedAtts } : merged;
|
|
8335
|
+
engine.send(s.id, payload, { userAction: true });
|
|
8336
|
+
sbQueue.delete(folder);
|
|
8337
|
+
clearTimeout(q.timer);
|
|
8338
|
+
}
|
|
8339
|
+
|
|
8340
|
+
function sbFlushOnDone(ev) {
|
|
8341
|
+
if (!ev || (ev.type !== 'done' && ev.type !== 'error') || !ev.sessionId) return;
|
|
8342
|
+
for (const [folder, sid] of sbSessions) {
|
|
8343
|
+
if (String(sid) === String(ev.sessionId) && sbQueue.has(folder)) {
|
|
8344
|
+
setTimeout(() => { try { sbFlush(folder); } catch {} }, 150);
|
|
8345
|
+
}
|
|
8346
|
+
}
|
|
8347
|
+
}
|
|
8348
|
+
|
|
8349
|
+
/* repo adresten (url | owner/repo) normalize et */
|
|
8350
|
+
function sbRepoFromInput(input) {
|
|
8351
|
+
let s = String(input || '').trim().replace(/\.git$/, '');
|
|
8352
|
+
if (!s) return null;
|
|
8353
|
+
const m = /^(?:https?:\/\/)?(?:www\.)?github\.com\/([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)/i.exec(s);
|
|
8354
|
+
if (m) return { url: 'https://github.com/' + m[1] + '/' + m[2] + '.git', name: m[2] };
|
|
8355
|
+
if (/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(s)) {
|
|
8356
|
+
const parts = s.split('/');
|
|
8357
|
+
return { url: 'https://github.com/' + parts[0] + '/' + parts[1] + '.git', name: parts[1] };
|
|
8358
|
+
}
|
|
8359
|
+
return null;
|
|
8360
|
+
}
|
|
8361
|
+
|
|
8362
|
+
ipcMain.handle('sandbox:list', async () => {
|
|
8363
|
+
const root = sandboxRoot();
|
|
8364
|
+
const items = [];
|
|
8365
|
+
try {
|
|
8366
|
+
sbLoadMap();
|
|
8367
|
+
for (const e of fs.readdirSync(root, { withFileTypes: true })) {
|
|
8368
|
+
if (!e.isDirectory() || e.name.startsWith('.')) continue;
|
|
8369
|
+
const folder = path.join(root, e.name);
|
|
8370
|
+
let sid = '';
|
|
8371
|
+
let busy = false;
|
|
8372
|
+
const known = sbSessions.get(folder);
|
|
8373
|
+
if (known) {
|
|
8374
|
+
sid = String(known);
|
|
8375
|
+
try { busy = engine.isBusy(known); } catch {}
|
|
8376
|
+
}
|
|
8377
|
+
items.push({ name: e.name, folder, sid, busy });
|
|
8378
|
+
}
|
|
8379
|
+
} catch {}
|
|
8380
|
+
return { ok: true, root, items };
|
|
8381
|
+
});
|
|
8382
|
+
|
|
8383
|
+
ipcMain.handle('sandbox:clone', async (_e, input) => {
|
|
8384
|
+
const repo = sbRepoFromInput(input);
|
|
8385
|
+
if (!repo) return { ok: false, error: 'github.com/owner/repo, owner/repo ya da tam .git adresi gir' };
|
|
8386
|
+
const root = sandboxRoot();
|
|
8387
|
+
const base = String(repo.name || 'repo').replace(/[\\/:*?"<>|]/g, '_').slice(0, 60) || 'repo';
|
|
8388
|
+
let dest = path.join(root, base);
|
|
8389
|
+
let n = 2;
|
|
8390
|
+
while (fs.existsSync(dest)) dest = path.join(root, base + '-' + n++);
|
|
8391
|
+
const finalName = path.basename(dest);
|
|
8392
|
+
return new Promise((resolve) => {
|
|
8393
|
+
const { spawn } = require('child_process');
|
|
8394
|
+
let settled = false;
|
|
8395
|
+
let lastPct = -1;
|
|
8396
|
+
let lastEmit = 0;
|
|
8397
|
+
let errText = '';
|
|
8398
|
+
/* git ilerlemeyi stderr'a \r ile AYNI satıra yazar (Receiving objects: %34 …)
|
|
8399
|
+
— her karede yüzde yakalanır, panele canlı akar */
|
|
8400
|
+
const onChunk = (buf) => {
|
|
8401
|
+
const txt = String(buf);
|
|
8402
|
+
errText += txt;
|
|
8403
|
+
const frames = txt.split(/\r/);
|
|
8404
|
+
const cur = (frames[frames.length - 1] || '').trim();
|
|
8405
|
+
const m = /(\d{1,3})%/.exec(cur);
|
|
8406
|
+
const pct = m ? Math.min(99, Number(m[1])) : null;
|
|
8407
|
+
const now = Date.now();
|
|
8408
|
+
if (cur && (pct !== null ? pct !== lastPct : now - lastEmit > 400)) {
|
|
8409
|
+
lastPct = pct != null ? pct : lastPct;
|
|
8410
|
+
lastEmit = now;
|
|
8411
|
+
try {
|
|
8412
|
+
if (win && !win.isDestroyed()) {
|
|
8413
|
+
win.webContents.send('agent:event', { type: 'sb-clone', pct, text: cur.slice(0, 90) });
|
|
8414
|
+
}
|
|
8415
|
+
} catch {}
|
|
8416
|
+
}
|
|
8417
|
+
};
|
|
8418
|
+
let proc;
|
|
8419
|
+
try {
|
|
8420
|
+
proc = spawn('git', ['clone', '--progress', repo.url, dest], { windowsHide: true });
|
|
8421
|
+
} catch (e) {
|
|
8422
|
+
return resolve({ ok: false, error: 'git başlatılamadı: ' + String((e && e.message) || e) });
|
|
8423
|
+
}
|
|
8424
|
+
proc.stdout.on('data', onChunk);
|
|
8425
|
+
proc.stderr.on('data', onChunk);
|
|
8426
|
+
const done = (err) => {
|
|
8427
|
+
if (settled) return;
|
|
8428
|
+
settled = true;
|
|
8429
|
+
try {
|
|
8430
|
+
if (win && !win.isDestroyed()) win.webContents.send('agent:event', { type: 'sb-clone', pct: null, done: !err });
|
|
8431
|
+
} catch {}
|
|
8432
|
+
if (err) {
|
|
8433
|
+
try { fs.rmSync(dest, { recursive: true, force: true }); } catch {}
|
|
8434
|
+
const estr = errText || String((err && err.message) || err);
|
|
8435
|
+
const msg = err && err.code === 'ENOENT'
|
|
8436
|
+
? 'git kurulu değil — https://git-scm.com adresinden kur'
|
|
8437
|
+
: /not found|does not exist|Could not/i.test(estr)
|
|
8438
|
+
? 'repo bulunamadı ya da erişilemedi (gizli repo mu?)'
|
|
8439
|
+
: /Authentication|403|Permission/i.test(estr)
|
|
8440
|
+
? 'erişim reddedildi (gizli repo ya da ağ sorunu)'
|
|
8441
|
+
: (estr.split('\n').filter((l) => l.trim()).pop() || 'klonlama başarısız').slice(0, 200);
|
|
8442
|
+
resolve({ ok: false, error: msg });
|
|
8443
|
+
return;
|
|
8444
|
+
}
|
|
8445
|
+
log.info('main', 'sandbox: klonlandı ' + repo.url + ' → ' + finalName);
|
|
8446
|
+
resolve({ ok: true, name: finalName, folder: dest });
|
|
8447
|
+
};
|
|
8448
|
+
proc.on('error', done);
|
|
8449
|
+
proc.on('close', (code) => done(code === 0 ? null : { code }));
|
|
8450
|
+
/* güvenlik tavanı: 10 dk */
|
|
8451
|
+
setTimeout(() => {
|
|
8452
|
+
if (!settled) {
|
|
8453
|
+
try { proc.kill(); } catch {}
|
|
8454
|
+
done(new Error('zaman aşımı'));
|
|
8455
|
+
}
|
|
8456
|
+
}, 600000);
|
|
8457
|
+
});
|
|
8458
|
+
});
|
|
8459
|
+
|
|
8460
|
+
ipcMain.handle('sandbox:send', async (_e, payload) => {
|
|
8461
|
+
const text = String((payload && payload.msg) || '').trim();
|
|
8462
|
+
const attachments = Array.isArray(payload && payload.attachments) ? payload.attachments : [];
|
|
8463
|
+
const folder = String((payload && payload.folder) || '');
|
|
8464
|
+
if (!folder) return { ok: false, error: 'repo seçilmedi' };
|
|
8465
|
+
if (!folder.startsWith(sandboxRoot() + path.sep)) return { ok: false, error: 'geçersiz klasör' };
|
|
8466
|
+
if (!text && !attachments.length) return { ok: false, error: 'boş mesaj' };
|
|
8467
|
+
if (!engine) return { ok: false, error: 'ajan hazır değil' };
|
|
8468
|
+
if (!engine.publicState().hasModel) return { ok: false, error: 'model yok — Ayarlar → Provider sekmesinden ekle' };
|
|
8469
|
+
const s = sandboxGetSession(folder);
|
|
8470
|
+
s.workspace = folder;
|
|
8471
|
+
s.bcCode = true;
|
|
8472
|
+
s.sbSandbox = true;
|
|
8473
|
+
engine.cache.set(s.id, s);
|
|
8474
|
+
const busy = engine.isBusy(s.id);
|
|
8475
|
+
const q = sbQueuePush(folder, text, attachments);
|
|
8476
|
+
if (busy) return { ok: true, queued: true, count: q.msgs.length, sessionId: s.id };
|
|
8477
|
+
clearTimeout(q.timer);
|
|
8478
|
+
q.timer = setTimeout(() => { try { sbFlush(folder); } catch {} }, SB_DEBOUNCE_MS);
|
|
8479
|
+
return { ok: true, sessionId: s.id, pending: true };
|
|
8480
|
+
});
|
|
8481
|
+
|
|
8482
|
+
ipcMain.handle('sandbox:stop', async (_e, payload) => {
|
|
8483
|
+
const folder = String((payload && payload.folder) || '');
|
|
8484
|
+
sbLoadMap();
|
|
8485
|
+
const sid = sbSessions.get(folder);
|
|
8486
|
+
const q = sbQueue.get(folder);
|
|
8487
|
+
if (q) {
|
|
8488
|
+
clearTimeout(q.timer);
|
|
8489
|
+
sbQueue.delete(folder);
|
|
8490
|
+
}
|
|
8491
|
+
const wasBusy = sid ? engine.isBusy(sid) : false;
|
|
8492
|
+
let r = false;
|
|
8493
|
+
if (wasBusy) {
|
|
8494
|
+
try { r = engine.interrupt(sid, 'kullanıcı Sandbox panelinden ■ ile durdurdu'); } catch {}
|
|
8495
|
+
}
|
|
8496
|
+
return { ok: true, wasBusy, interrupted: r };
|
|
8497
|
+
});
|
|
8498
|
+
|
|
8499
|
+
ipcMain.handle('sandbox:new', async (_e, payload) => {
|
|
8500
|
+
const folder = String((payload && payload.folder) || '');
|
|
8501
|
+
sbLoadMap();
|
|
8502
|
+
const sid = sbSessions.get(folder);
|
|
8503
|
+
if (sid && engine.isBusy(sid)) return { ok: false, error: 'mesaj sürüyor — önce ■ ile durdur' };
|
|
8504
|
+
const q = sbQueue.get(folder);
|
|
8505
|
+
if (q) {
|
|
8506
|
+
clearTimeout(q.timer);
|
|
8507
|
+
sbQueue.delete(folder);
|
|
8508
|
+
}
|
|
8509
|
+
if (sid) {
|
|
8510
|
+
try { engine.deleteSession(sid); } catch {}
|
|
8511
|
+
sbSessions.delete(folder);
|
|
8512
|
+
sbSaveMap();
|
|
8513
|
+
}
|
|
8514
|
+
return { ok: true };
|
|
8515
|
+
});
|
|
8516
|
+
|
|
8517
|
+
/* repo klasörünü diskten sil (oturum kaydıyla birlikte temizlenir) */
|
|
8518
|
+
ipcMain.handle('sandbox:remove', async (_e, payload) => {
|
|
8519
|
+
const folder = String((payload && payload.folder) || '');
|
|
8520
|
+
if (!folder.startsWith(sandboxRoot() + path.sep)) return { ok: false, error: 'geçersiz klasör' };
|
|
8521
|
+
sbLoadMap();
|
|
8522
|
+
const sid = sbSessions.get(folder);
|
|
8523
|
+
if (sid && engine.isBusy(sid)) return { ok: false, error: 'repo çalışıyor — önce ■ ile durdur' };
|
|
8524
|
+
try { fs.rmSync(folder, { recursive: true, force: true }); } catch (e) {
|
|
8525
|
+
return { ok: false, error: String((e && e.message) || e) };
|
|
8526
|
+
}
|
|
8527
|
+
if (sid) {
|
|
8528
|
+
try { engine.deleteSession(sid); } catch {}
|
|
8529
|
+
sbSessions.delete(folder);
|
|
8530
|
+
sbSaveMap();
|
|
8531
|
+
}
|
|
8532
|
+
return { ok: true };
|
|
8533
|
+
});
|
|
8534
|
+
|
|
8535
|
+
ipcMain.handle('sandbox:reveal', async (_e, payload) => {
|
|
8536
|
+
const folder = String((payload && payload.folder) || '');
|
|
8537
|
+
if (!folder.startsWith(sandboxRoot() + path.sep)) return { ok: false, error: 'geçersiz klasör' };
|
|
8538
|
+
try { require('electron').shell.openPath(folder); return { ok: true }; } catch (e) {
|
|
8539
|
+
return { ok: false, error: String((e && e.message) || e) };
|
|
8540
|
+
}
|
|
8541
|
+
});
|
|
8542
|
+
|
|
8543
|
+
/* seçili reponun oturum geçmişini panele döndürür (yeni oturum yoksa oluşturur) */
|
|
8544
|
+
ipcMain.handle('sandbox:open', async (_e, payload) => {
|
|
8545
|
+
const folder = String((payload && payload.folder) || '');
|
|
8546
|
+
if (!folder.startsWith(sandboxRoot() + path.sep)) return { ok: false, error: 'geçersiz klasör' };
|
|
8547
|
+
if (!engine) return { ok: false, error: 'ajan hazır değil' };
|
|
8548
|
+
const s = sandboxGetSession(folder);
|
|
8549
|
+
s.workspace = folder;
|
|
8550
|
+
engine.cache.set(s.id, s);
|
|
8551
|
+
const msgs = [];
|
|
8552
|
+
for (const m of s.messages || []) {
|
|
8553
|
+
if (m.tool_calls) continue;
|
|
8554
|
+
const txt = bcMsgText(m);
|
|
8555
|
+
if (!txt) continue;
|
|
8556
|
+
msgs.push({ role: m.role === 'assistant' ? 'assistant' : 'user', text: txt.slice(0, 4000) });
|
|
8557
|
+
}
|
|
8558
|
+
return {
|
|
8559
|
+
ok: true,
|
|
8560
|
+
sessionId: s.id,
|
|
8561
|
+
busy: !!engine.isBusy(s.id),
|
|
8562
|
+
messages: msgs.slice(-200),
|
|
8563
|
+
};
|
|
8564
|
+
});
|
|
8565
|
+
|
|
8566
|
+
/* Sandbox dosya konsolu: seçili repo klasörünün ağacı (filePanel yeniden kullanılır) */
|
|
8567
|
+
ipcMain.handle('sandbox:tree', (_e, payload) => {
|
|
8568
|
+
const folder = String((payload && payload.folder) || '');
|
|
8569
|
+
if (!folder.startsWith(sandboxRoot() + path.sep)) return { ok: false, error: 'geçersiz klasör' };
|
|
8570
|
+
const p = path.resolve(folder, String((payload && payload.rel) || ''));
|
|
8571
|
+
if (p !== folder && !p.startsWith(folder + path.sep)) return { ok: false, error: 'geçersiz yol' };
|
|
8572
|
+
try {
|
|
8573
|
+
const entries = fs.readdirSync(p, { withFileTypes: true })
|
|
8574
|
+
.filter((e) => e.name !== 'node_modules' && e.name !== '.git')
|
|
8575
|
+
.map((e) => {
|
|
8576
|
+
let size = 0;
|
|
8577
|
+
if (!e.isDirectory()) {
|
|
8578
|
+
try { size = fs.statSync(path.join(p, e.name)).size; } catch {}
|
|
8579
|
+
}
|
|
8580
|
+
return { name: e.name, dir: e.isDirectory(), size };
|
|
8581
|
+
})
|
|
8582
|
+
.sort((a, b) => (a.dir === b.dir ? a.name.localeCompare(b.name) : a.dir ? -1 : 1));
|
|
8583
|
+
return { ok: true, workspace: folder, entries };
|
|
8584
|
+
} catch (e) {
|
|
8585
|
+
return { ok: false, error: String((e && e.message) || e) };
|
|
8586
|
+
}
|
|
8587
|
+
});
|
|
8588
|
+
|
|
8589
|
+
/* Sandbox ÇALIŞTIRICI: repo klasöründe uzun süreli komut (npm start/dev vb.)
|
|
8590
|
+
YÖNETİLEN SÜREÇ — çıktı satır satır panele akar; ■ ile ağaç-kullanı durdurulur.
|
|
8591
|
+
Dev server adresi çıktıdan yakalanır → 'Tarayıcıda aç' aktifleşir.
|
|
8592
|
+
Aynı fonksiyon engine.sbRunHook olarak da bağlanır — ajanın panel_run
|
|
8593
|
+
aracı turu KİLİTLEMEDEN süreç başlatır (Kur/Başlat butonları bunu kullanır). */
|
|
8594
|
+
const sbRunProc = { proc: null, folder: '', url: '' };
|
|
8595
|
+
|
|
8596
|
+
function sbRunEmit(data) {
|
|
8597
|
+
try {
|
|
8598
|
+
if (win && !win.isDestroyed()) win.webContents.send('agent:event', { type: 'sb-run', data: String(data || '') });
|
|
8599
|
+
} catch {}
|
|
8600
|
+
}
|
|
8601
|
+
|
|
8602
|
+
function sbRunStartManaged(folder, cmd) {
|
|
8603
|
+
folder = String(folder || '');
|
|
8604
|
+
cmd = String(cmd || '').trim();
|
|
8605
|
+
if (!folder.startsWith(sandboxRoot() + path.sep)) return { ok: false, error: 'geçersiz klasör' };
|
|
8606
|
+
if (!cmd) return { ok: false, error: 'komut boş' };
|
|
8607
|
+
if (sbRunProc.proc) return { ok: false, error: 'ÇALIŞTIR panelinde zaten bir süreç çalışıyor — önce ■ ile durdur' };
|
|
8608
|
+
const { spawn } = require('child_process');
|
|
8609
|
+
try {
|
|
8610
|
+
const proc = spawn('cmd.exe', ['/d', '/s', '/c', cmd], {
|
|
8611
|
+
cwd: folder,
|
|
8612
|
+
windowsHide: true,
|
|
8613
|
+
env: { ...process.env, FORCE_COLOR: '0', NO_COLOR: '1' },
|
|
8614
|
+
});
|
|
8615
|
+
sbRunProc.proc = proc;
|
|
8616
|
+
sbRunProc.folder = folder;
|
|
8617
|
+
sbRunProc.url = '';
|
|
8618
|
+
const onLine = (buf) => {
|
|
8619
|
+
for (const piece of String(buf).split(/\r?\n/)) {
|
|
8620
|
+
if (!piece.trim()) continue;
|
|
8621
|
+
sbRunEmit(piece);
|
|
8622
|
+
/* dev server adresi yakala (sadece localhost) */
|
|
8623
|
+
const m = /https?:\/\/(?:localhost|127\.0\.0\.1)(?::\d{2,5})?[^\s'"<>]*/i.exec(piece);
|
|
8624
|
+
if (m && !sbRunProc.url) {
|
|
8625
|
+
sbRunProc.url = m[0].replace(/[)\].,;:'"]+$/, '');
|
|
8626
|
+
try {
|
|
8627
|
+
if (win && !win.isDestroyed()) win.webContents.send('agent:event', { type: 'sb-run-url', url: sbRunProc.url });
|
|
8628
|
+
} catch {}
|
|
8629
|
+
}
|
|
8630
|
+
}
|
|
8631
|
+
};
|
|
8632
|
+
proc.stdout.on('data', onLine);
|
|
8633
|
+
proc.stderr.on('data', onLine);
|
|
8634
|
+
proc.on('close', (code) => {
|
|
8635
|
+
sbRunEmit('[süreç bitti' + (code != null ? ' — kod ' + code : '') + ']');
|
|
8636
|
+
sbRunProc.proc = null;
|
|
8637
|
+
try {
|
|
8638
|
+
if (win && !win.isDestroyed()) win.webContents.send('agent:event', { type: 'sb-run-end' });
|
|
8639
|
+
} catch {}
|
|
8640
|
+
});
|
|
8641
|
+
sbRunEmit('▶ ' + cmd + ' (klasör: ' + path.basename(folder) + ')');
|
|
8642
|
+
return {
|
|
8643
|
+
ok: true,
|
|
8644
|
+
note: 'süreç ÇALIŞTIR panelinde başlatıldı — çıktı orada canlı akar; durdurmak için paneldeki ■. Kullanıcıya adres varsa bildir.',
|
|
8645
|
+
};
|
|
8646
|
+
} catch (e) {
|
|
8647
|
+
sbRunProc.proc = null;
|
|
8648
|
+
return { ok: false, error: String((e && e.message) || e) };
|
|
8649
|
+
}
|
|
8650
|
+
}
|
|
8651
|
+
|
|
8652
|
+
ipcMain.handle('sandbox:run', async (_e, payload) => {
|
|
8653
|
+
const folder = String((payload && payload.folder) || '');
|
|
8654
|
+
const cmd = String((payload && payload.cmd) || '').trim();
|
|
8655
|
+
return sbRunStartManaged(folder, cmd);
|
|
8656
|
+
});
|
|
8657
|
+
|
|
8658
|
+
ipcMain.handle('sandbox:runstop', async () => {
|
|
8659
|
+
const p = sbRunProc.proc;
|
|
8660
|
+
if (!p) return { ok: true, wasRunning: false };
|
|
8661
|
+
sbRunProc.proc = null;
|
|
8662
|
+
try {
|
|
8663
|
+
/* cmd.exe /c zinciriyle çocuklar (node/vite) geride kalmasın — ağaç kesimi */
|
|
8664
|
+
const { exec } = require('child_process');
|
|
8665
|
+
if (process.platform === 'win32') exec('taskkill /pid ' + p.pid + ' /T /F', { windowsHide: true });
|
|
8666
|
+
else p.kill('SIGTERM');
|
|
8667
|
+
} catch {}
|
|
8668
|
+
return { ok: true, wasRunning: true };
|
|
8669
|
+
});
|
|
8670
|
+
|
|
8671
|
+
ipcMain.handle('sandbox:openurl', async (_e, url) => {
|
|
8672
|
+
const u = String(url || '').trim();
|
|
8673
|
+
if (!/^https?:\/\/(?:localhost|127\.0\.0\.1)(?::\d{2,5})?(?:\/|$)/i.test(u)) return { ok: false, error: 'yalnız localhost adresleri açılabilir' };
|
|
8674
|
+
try {
|
|
8675
|
+
setBrowserOpen(true, true);
|
|
8676
|
+
browser.view.webContents.loadURL(u).catch(() => {});
|
|
8677
|
+
return { ok: true, url: u };
|
|
8678
|
+
} catch (e) {
|
|
8679
|
+
return { ok: false, error: String((e && e.message) || e) };
|
|
8680
|
+
}
|
|
8681
|
+
});
|
|
8682
|
+
|
|
8229
8683
|
/* Sağ tık menüsü: HTML dosyasını dahili tarayıcıda GÖRÜNÜR aç */
|
|
8230
8684
|
/* ---------- BC DAHİLİ STATİK SUNUCU ----------
|
|
8231
8685
|
Beast Code çıktısı ASLA file:// ile açılmaz: ES modülleri, fetch, ServiceWorker
|
|
@@ -8641,8 +9095,8 @@ async function fetchZenFreeModels() {
|
|
|
8641
9095
|
if (free.length) return free;
|
|
8642
9096
|
}
|
|
8643
9097
|
} catch {}
|
|
8644
|
-
/* API cevap vermezse bilinen free liste */
|
|
8645
|
-
return ['big-pickle', 'deepseek-v4-flash-free', 'mimo-v2.5-free', '
|
|
9098
|
+
/* API cevap vermezse bilinen free liste (09/2026 güncel — eski adlar düşürüldü) */
|
|
9099
|
+
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
9100
|
}
|
|
8647
9101
|
|
|
8648
9102
|
function upsertZenEnv(token) {
|
package/src/preload.js
CHANGED
|
@@ -139,6 +139,19 @@ contextBridge.exposeInMainWorld('beast', {
|
|
|
139
139
|
fetchModels: (opts) => ipcRenderer.invoke('custom:fetchModels', opts),
|
|
140
140
|
builtinProviders: () => ipcRenderer.invoke('providers:builtin'),
|
|
141
141
|
zenOneClick: () => ipcRenderer.invoke('zen:oneClick'),
|
|
142
|
+
|
|
143
|
+
sandboxList: () => ipcRenderer.invoke('sandbox:list'),
|
|
144
|
+
sandboxClone: (input) => ipcRenderer.invoke('sandbox:clone', input),
|
|
145
|
+
sandboxSend: (payload) => ipcRenderer.invoke('sandbox:send', payload),
|
|
146
|
+
sandboxStop: (payload) => ipcRenderer.invoke('sandbox:stop', payload),
|
|
147
|
+
sandboxNew: (payload) => ipcRenderer.invoke('sandbox:new', payload),
|
|
148
|
+
sandboxRemove: (payload) => ipcRenderer.invoke('sandbox:remove', payload),
|
|
149
|
+
sandboxReveal: (payload) => ipcRenderer.invoke('sandbox:reveal', payload),
|
|
150
|
+
sandboxOpen: (payload) => ipcRenderer.invoke('sandbox:open', payload),
|
|
151
|
+
sandboxTree: (payload) => ipcRenderer.invoke('sandbox:tree', payload),
|
|
152
|
+
sandboxRun: (payload) => ipcRenderer.invoke('sandbox:run', payload),
|
|
153
|
+
sandboxRunStop: () => ipcRenderer.invoke('sandbox:runstop'),
|
|
154
|
+
sandboxOpenUrl: (url) => ipcRenderer.invoke('sandbox:openurl', url),
|
|
142
155
|
waStart: () => ipcRenderer.invoke('wa:start'),
|
|
143
156
|
waStop: () => ipcRenderer.invoke('wa:stop'),
|
|
144
157
|
waReset: () => ipcRenderer.invoke('wa:reset'),
|
package/src/renderer/i18n.js
CHANGED
|
@@ -579,6 +579,7 @@
|
|
|
579
579
|
tipGithub: 'GitHub Trending — repoları favorile, chate at, ajana araştır',
|
|
580
580
|
tipIde: 'IDE Modu — dosyalar + chat + preview',
|
|
581
581
|
tipStudio: 'Beast Studio — video yapma/düzenleme modu',
|
|
582
|
+
tipSandbox: 'Beast Sandbox — GitHub reposu indir & içinde çalış',
|
|
582
583
|
tipCodeHide: 'Editörü kapat',
|
|
583
584
|
tipCodeShow: 'Editörü göster',
|
|
584
585
|
ide_files: 'DOSYALAR',
|
|
@@ -1268,6 +1269,7 @@
|
|
|
1268
1269
|
tipGithub: 'GitHub Trending — favorite repos, send to chat, ask the agent',
|
|
1269
1270
|
tipIde: 'IDE Mode — files + chat + preview',
|
|
1270
1271
|
tipStudio: 'Beast Studio — video making/editing mode',
|
|
1272
|
+
tipSandbox: 'Beast Sandbox — download a GitHub repo & work inside it',
|
|
1271
1273
|
tipCodeHide: 'Hide editor',
|
|
1272
1274
|
tipCodeShow: 'Show editor',
|
|
1273
1275
|
ide_files: 'FILES',
|