beast-agent 2.10.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 +556 -9
- 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/src/main.js
CHANGED
|
@@ -75,6 +75,73 @@ function emitUpdateEvent() {
|
|
|
75
75
|
} catch {}
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
/* /version ORTAK METNİ — tüm kanallar (desktop/WA/TG/DC): güncel sürüm +
|
|
79
|
+
npm'deki en son sürüm (yeni varsa vurgulu; 10dk cache'li getNpmLatest). */
|
|
80
|
+
async function versionText(waStyle) {
|
|
81
|
+
const cur = beastVersion();
|
|
82
|
+
const latest = await getNpmLatest();
|
|
83
|
+
const b = waStyle ? '*' : '**';
|
|
84
|
+
if (latest && isNewerVersion(latest, cur)) {
|
|
85
|
+
return (
|
|
86
|
+
`${b}Beast Agent v${cur}${b} — ${b}yeni sürüm var: v${latest}${b}\n` +
|
|
87
|
+
`Güncelleme: ${b}/update${b} (kontrol) · ${b}/update now${b} (hemen kur)`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
if (latest) {
|
|
91
|
+
return `${b}Beast Agent v${cur}${b} — güncel ✓\nGüncelleme kontrolü: ${b}/update${b} · hemen kur: ${b}/update now${b}`;
|
|
92
|
+
}
|
|
93
|
+
return `${b}Beast Agent v${cur}${b}\nGüncelleme: ${b}/update${b} (yeni sürüm kontrolü) · ${b}/update now${b} (hemen kur)`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* SÜRÜM GÜNCELLEMESİ BİLGİSİ: sürüm değiştiyse İLK AÇILIŞTA kullanıcı'nın
|
|
97
|
+
olduğu yere kısa bilgi düşer — en güncel oturum (desktop) + o oturuma
|
|
98
|
+
BĞLI kanal (WA/TG/DC; hangisindeyse oraya). settings.lastRunVersion
|
|
99
|
+
yazıldığı için sürüm başına YALNIZ BİR KEZ çalışır. */
|
|
100
|
+
function notifyVersionUpdate() {
|
|
101
|
+
const cur = beastVersion();
|
|
102
|
+
const prev = String(settings.lastRunVersion || '');
|
|
103
|
+
settings.lastRunVersion = cur;
|
|
104
|
+
saveSettings();
|
|
105
|
+
if (!prev || prev === cur) return; /* ilk kurulum ya da sürüm değişmedi */
|
|
106
|
+
const waStyleTxt =
|
|
107
|
+
`🔄 *Beast Agent güncellendi:* v${prev} → *v${cur}*\n` +
|
|
108
|
+
`Yenilikler: GitHub Releases · Sürüm: /version · Güncelleme: /update`;
|
|
109
|
+
let sid = '';
|
|
110
|
+
try { sid = reuseOrLatestSession(''); } catch {}
|
|
111
|
+
/* desktop: en güncel oturuma bilgi balonu */
|
|
112
|
+
try {
|
|
113
|
+
if (win && !win.isDestroyed() && sid) {
|
|
114
|
+
const mdTxt = waStyleTxt.replace(/\*/g, '**');
|
|
115
|
+
win.webContents.send('agent:event', { sessionId: sid, type: 'message', message: { role: 'assistant', content: mdTxt } });
|
|
116
|
+
win.webContents.send('agent:event', { sessionId: sid, type: 'done', usage: null });
|
|
117
|
+
}
|
|
118
|
+
} catch {}
|
|
119
|
+
/* kanallar: SADECE bu oturuma bağlı kanal (kullanıcı oradaydı) */
|
|
120
|
+
(async () => {
|
|
121
|
+
try {
|
|
122
|
+
if (wa && wa.connected) {
|
|
123
|
+
const own = waOwnerNum();
|
|
124
|
+
const jid = own ? own + '@s.whatsapp.net' : '';
|
|
125
|
+
if (jid && [...waChats.entries()].some(([j, s]) => j === jid && String(s) === String(sid))) {
|
|
126
|
+
await sendWaSafe(jid, waStyleTxt);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
} catch {}
|
|
130
|
+
try {
|
|
131
|
+
if (tg && tg.connected) {
|
|
132
|
+
const hit = [...tgChats.entries()].find(([, s]) => String(s) === String(sid));
|
|
133
|
+
if (hit) await sendTgSafe(hit[0], waStyleTxt);
|
|
134
|
+
}
|
|
135
|
+
} catch {}
|
|
136
|
+
try {
|
|
137
|
+
if (dc && dc.connected) {
|
|
138
|
+
const hit = [...dcChats.entries()].find(([, s]) => String(s) === String(sid));
|
|
139
|
+
if (hit) await sendDcSafe(hit[0], waStyleTxt);
|
|
140
|
+
}
|
|
141
|
+
} catch {}
|
|
142
|
+
})();
|
|
143
|
+
}
|
|
144
|
+
|
|
78
145
|
function replyUpdate(text) {
|
|
79
146
|
try {
|
|
80
147
|
for (const sid of updateReplies.sids) desktopEcho(sid, '/update', text);
|
|
@@ -1623,7 +1690,8 @@ async function tryWaSlash(jid, rawText, senderNum, payload0) {
|
|
|
1623
1690
|
out = 'Bu komut yalnız BeastCode modunda çalışır — önce /beastcode yaz.';
|
|
1624
1691
|
}
|
|
1625
1692
|
} else if (cmd === 'version') {
|
|
1626
|
-
|
|
1693
|
+
/* npm'deki en son sürümü de göster */
|
|
1694
|
+
out = await versionText(true);
|
|
1627
1695
|
} else {
|
|
1628
1696
|
out = `Bilinmeyen komut: /${cmd}\nListe için /help yaz.`;
|
|
1629
1697
|
}
|
|
@@ -2643,6 +2711,14 @@ async function processTgMessage(chatId, payload, requeues = 0) {
|
|
|
2643
2711
|
tgLog(`skip flush: izinli eşleşme yok (sender=${payload.senderId || '?'})`);
|
|
2644
2712
|
return;
|
|
2645
2713
|
}
|
|
2714
|
+
/* /version HER KANALDA: sürüm + npm'deki en son sürüm (izin kontrolü üstte) */
|
|
2715
|
+
const tgRaw = String((payload && payload.text) || '').trim();
|
|
2716
|
+
if (tgRaw === '/version' || tgRaw.startsWith('/version ')) {
|
|
2717
|
+
versionText(true)
|
|
2718
|
+
.then((txt) => sendTgSafe(chatId, txt))
|
|
2719
|
+
.catch(() => {});
|
|
2720
|
+
return;
|
|
2721
|
+
}
|
|
2646
2722
|
/* OPENCODE STEER: meşgulse bekleme — engine.send tampona ekler, koşan tur
|
|
2647
2723
|
sonraki istekte görür (WA ile aynı mantık) */
|
|
2648
2724
|
let sid = tgChats.get(chatId) || tgSingleSid;
|
|
@@ -2854,6 +2930,14 @@ async function processDcMessage(channelId, payload) {
|
|
|
2854
2930
|
dcLog(`skip flush: izinli eşleşme yok (sender=${payload.senderId || '?'})`);
|
|
2855
2931
|
return;
|
|
2856
2932
|
}
|
|
2933
|
+
/* /version HER KANALDA: sürüm + npm'deki en son sürüm (izin kontrolü üstte) */
|
|
2934
|
+
const dcRaw = String((payload && payload.text) || '').trim();
|
|
2935
|
+
if (dcRaw === '/version' || dcRaw.startsWith('/version ')) {
|
|
2936
|
+
versionText(true)
|
|
2937
|
+
.then((txt) => sendDcSafe(channelId, txt))
|
|
2938
|
+
.catch(() => {});
|
|
2939
|
+
return;
|
|
2940
|
+
}
|
|
2857
2941
|
/* OPENCODE STEER: meşgulse bekleme — engine.send tampona ekler, koşan tur
|
|
2858
2942
|
sonraki istekte görür (WA/TG ile aynı mantık) */
|
|
2859
2943
|
let sid = dcChats.get(channelId) || dcSingleSid;
|
|
@@ -3019,6 +3103,7 @@ function reloadBackend() {
|
|
|
3019
3103
|
flushDesktopOnDone(ev); /* biriken desktop mesajlarını sıraya bas */
|
|
3020
3104
|
bcFlushOnDone(ev); /* Beast Code kuyruğunu iş bitiminde boşalt */
|
|
3021
3105
|
stFlushOnDone(ev); /* Beast Studio kuyruğunu iş bitiminde boşalt */
|
|
3106
|
+
sbFlushOnDone(ev); /* Sandbox kuyruğunu iş bitiminde boşalt */
|
|
3022
3107
|
/* BC canlı önizleme: ajan bir dev server başlattıysa adresi yakala —
|
|
3023
3108
|
preview butonu ve otomatik açılış DAİMA bu sunucuyu öncelikli kullanır */
|
|
3024
3109
|
if (ev.type === 'bc-preview' && /^https?:\/\/(?:localhost|127\.0\.0\.1)(?::\d{2,5})?/i.test(String(ev.url || ''))) {
|
|
@@ -3200,6 +3285,9 @@ function reloadBackend() {
|
|
|
3200
3285
|
}
|
|
3201
3286
|
},
|
|
3202
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;
|
|
3203
3291
|
return engine.publicState();
|
|
3204
3292
|
}
|
|
3205
3293
|
|
|
@@ -3413,6 +3501,12 @@ app.whenReady().then(() => {
|
|
|
3413
3501
|
ensureDc().start().catch((e) => dcLog('autostart failed: ' + String((e && e.message) || e)));
|
|
3414
3502
|
}
|
|
3415
3503
|
|
|
3504
|
+
/* SÜRÜM GÜNCELLENDİYSE ilk açılışta bilgi: kanalların bağlanmasına 5 sn tanı,
|
|
3505
|
+
sonra kullanıcının olduğu yere (en güncel oturum + bağlı kanal) düşer */
|
|
3506
|
+
setTimeout(() => {
|
|
3507
|
+
try { notifyVersionUpdate(); } catch {}
|
|
3508
|
+
}, 5000);
|
|
3509
|
+
|
|
3416
3510
|
app.on('activate', () => {
|
|
3417
3511
|
if (BrowserWindow.getAllWindows().length === 0) createWindow();
|
|
3418
3512
|
});
|
|
@@ -4808,7 +4902,10 @@ ipcMain.handle('agent:send', (_e, { sessionId, text }) => {
|
|
|
4808
4902
|
return true;
|
|
4809
4903
|
}
|
|
4810
4904
|
if (t === '/version') {
|
|
4811
|
-
|
|
4905
|
+
/* npm registry'den en son sürümü de göster (10dk cache'li) */
|
|
4906
|
+
versionText(false)
|
|
4907
|
+
.then((txt) => desktopEcho(sessionId, t, txt))
|
|
4908
|
+
.catch(() => desktopEcho(sessionId, t, `**Beast Agent v${beastVersion()}**`));
|
|
4812
4909
|
return true;
|
|
4813
4910
|
}
|
|
4814
4911
|
if (t === '/help') {
|
|
@@ -7366,9 +7463,10 @@ ipcMain.handle('beastcode:send', async (_e, payload) => {
|
|
|
7366
7463
|
return { ok: true, sessionId: s.id, mode: s.bcMode };
|
|
7367
7464
|
}
|
|
7368
7465
|
if (busy) {
|
|
7369
|
-
/*
|
|
7370
|
-
|
|
7371
|
-
|
|
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 };
|
|
7372
7470
|
}
|
|
7373
7471
|
/* boşta: kısa pencere — hızlı ard arda mesajlar tek işte birleşir */
|
|
7374
7472
|
const q = bcQueuePush(ws, text, attachments);
|
|
@@ -7652,8 +7750,9 @@ ipcMain.handle('studio:send', async (_e, payload) => {
|
|
|
7652
7750
|
return { ok: true, sessionId: s.id, mode: s.bcMode };
|
|
7653
7751
|
}
|
|
7654
7752
|
if (busy) {
|
|
7655
|
-
|
|
7656
|
-
|
|
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 };
|
|
7657
7756
|
}
|
|
7658
7757
|
const q = stQueuePush(ws, text, attachments);
|
|
7659
7758
|
clearTimeout(q.timer);
|
|
@@ -8133,6 +8232,454 @@ ipcMain.handle('studio:delete', async (_e, rel) => {
|
|
|
8133
8232
|
}
|
|
8134
8233
|
});
|
|
8135
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
|
+
|
|
8136
8683
|
/* Sağ tık menüsü: HTML dosyasını dahili tarayıcıda GÖRÜNÜR aç */
|
|
8137
8684
|
/* ---------- BC DAHİLİ STATİK SUNUCU ----------
|
|
8138
8685
|
Beast Code çıktısı ASLA file:// ile açılmaz: ES modülleri, fetch, ServiceWorker
|
|
@@ -8548,8 +9095,8 @@ async function fetchZenFreeModels() {
|
|
|
8548
9095
|
if (free.length) return free;
|
|
8549
9096
|
}
|
|
8550
9097
|
} catch {}
|
|
8551
|
-
/* API cevap vermezse bilinen free liste */
|
|
8552
|
-
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'];
|
|
8553
9100
|
}
|
|
8554
9101
|
|
|
8555
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',
|