beast-agent 1.9.0 → 2.0.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/LICENSE +21 -21
- package/README.md +130 -130
- package/bin/beast-agent.js +137 -137
- package/package.json +1 -1
- package/scripts/fix-electron.js +138 -138
- package/scripts/release.js +137 -131
- package/scripts/swap-electron.js +40 -40
- package/src/agent/agentdefs.js +122 -122
- package/src/agent/bots.js +580 -580
- package/src/agent/bus.js +389 -389
- package/src/agent/computeruse.js +200 -200
- package/src/agent/config.js +284 -284
- package/src/agent/discord.js +332 -332
- package/src/agent/engine.js +75 -10
- package/src/agent/kb.js +123 -123
- package/src/agent/llm.js +430 -430
- package/src/agent/logger.js +90 -90
- package/src/agent/mcp.js +427 -427
- package/src/agent/mem0.js +605 -605
- package/src/agent/memory.js +427 -427
- package/src/agent/mqueue.js +124 -124
- package/src/agent/pdf.js +20 -20
- package/src/agent/research.js +133 -133
- package/src/agent/scripts/news.py +113 -113
- package/src/agent/scripts/stealthsearch.py +30 -30
- package/src/agent/scripts/websearch.py +225 -225
- package/src/agent/searxng.js +325 -325
- package/src/agent/seeds/brainstorming/SKILL.md +90 -90
- package/src/agent/seeds/dispatching-parallel-agents/SKILL.md +120 -120
- package/src/agent/seeds/executing-plans/SKILL.md +60 -60
- package/src/agent/seeds/subagent-driven-development/SKILL.md +167 -167
- package/src/agent/seeds/systematic-debugging/SKILL.md +131 -131
- package/src/agent/seeds/test-driven-development/SKILL.md +152 -152
- package/src/agent/seeds/verification-before-completion/SKILL.md +63 -63
- package/src/agent/seeds/writing-plans/SKILL.md +162 -162
- package/src/agent/seeds/writing-skills/SKILL.md +229 -229
- package/src/agent/skills.js +652 -652
- package/src/agent/store.js +378 -378
- package/src/agent/telegram.js +155 -155
- package/src/agent/tokens.js +39 -39
- package/src/agent/usage.js +125 -125
- package/src/agent/watchers.js +312 -312
- package/src/agent/watext.js +80 -80
- package/src/agent/whatsapp.js +555 -555
- package/src/cron.js +255 -255
- package/src/main.js +348 -6
- package/src/preload.js +9 -0
- package/src/renderer/browserPreload.js +73 -73
- package/src/renderer/i18n.js +4 -0
- package/src/renderer/index.html +448 -409
- package/src/renderer/renderer.js +824 -41
- package/src/renderer/style.css +264 -5
package/src/main.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { app, BrowserWindow, WebContentsView, ipcMain, shell, dialog, Tray, Menu, nativeImage, desktopCapturer, session, net: electronNet } = require('electron');
|
|
3
|
+
const { app, BrowserWindow, WebContentsView, ipcMain, shell, dialog, Tray, Menu, nativeImage, desktopCapturer, session, net: electronNet, clipboard } = require('electron');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const http = require('http');
|
|
@@ -2617,6 +2617,7 @@ function reloadBackend() {
|
|
|
2617
2617
|
if (win && !win.isDestroyed()) win.webContents.send('agent:event', ev);
|
|
2618
2618
|
flushDesktopOnDone(ev); /* biriken desktop mesajlarını sıraya bas */
|
|
2619
2619
|
bcFlushOnDone(ev); /* Beast Code kuyruğunu iş bitiminde boşalt */
|
|
2620
|
+
stFlushOnDone(ev); /* Beast Studio kuyruğunu iş bitiminde boşalt */
|
|
2620
2621
|
/* BC canlı önizleme: ajan bir dev server başlattıysa adresi yakala —
|
|
2621
2622
|
preview butonu ve otomatik açılış DAİMA bu sunucuyu öncelikli kullanır */
|
|
2622
2623
|
if (ev.type === 'bc-preview' && /^https?:\/\/(?:localhost|127\.0\.0\.1)(?::\d{2,5})?/i.test(String(ev.url || ''))) {
|
|
@@ -2932,6 +2933,7 @@ app.whenReady().then(() => {
|
|
|
2932
2933
|
watchers.start({ onTrigger: watcherFire });
|
|
2933
2934
|
startEventBus();
|
|
2934
2935
|
ideWatchStart(); // soldaki dosya ağacı canlı izlemede
|
|
2936
|
+
studioWatchStart(); // Beast Studio klasörü canlı izlemede
|
|
2935
2937
|
maybeRunWhereWasI();
|
|
2936
2938
|
falloutResume();
|
|
2937
2939
|
startAutoUpdater(); // #3 sessiz güncelleme
|
|
@@ -5857,12 +5859,304 @@ ipcMain.handle('beastcode:new', async () => {
|
|
|
5857
5859
|
return { ok: true };
|
|
5858
5860
|
});
|
|
5859
5861
|
|
|
5862
|
+
/* ---------------- Beast Studio paneli (video yapma/düzenleme modu) ----------------
|
|
5863
|
+
Beast Code ile AYRI ve ÖZEL dünya: kendi çalışma klasörü (studioRoot), kendi
|
|
5864
|
+
gizli engine oturumları (klasör bazlı), kendi sohbet kuyruğu. Studio
|
|
5865
|
+
oturumları chat geçmişinde VE Beast Code panelinde ASLA görünmez; Beast Code
|
|
5866
|
+
oturumları da Studio'ya sızmaz. Ajan studio=true ile açılır → buildStudioSystem:
|
|
5867
|
+
klasör içeriğini video proje malzemesi olarak görür (ffmpeg/ffprobe farkında). */
|
|
5868
|
+
const studioSessions = new Map(); /* klasör yolu → sessionId */
|
|
5869
|
+
|
|
5870
|
+
function studioRoot() {
|
|
5871
|
+
/* Studio'nun KENDİ klasörü — IDE'nin ideRoot'undan bağımsız.
|
|
5872
|
+
Ayar yoksa videolar klasörü (Windows Videos) makul başlangıçtır. */
|
|
5873
|
+
if (settings.studioRoot) return path.resolve(settings.studioRoot);
|
|
5874
|
+
let v = '';
|
|
5875
|
+
try { v = app.getPath('videos'); } catch {}
|
|
5876
|
+
return path.resolve(v || settings.workspace || app.getPath('home'));
|
|
5877
|
+
}
|
|
5878
|
+
|
|
5879
|
+
function studioSafe(rel) {
|
|
5880
|
+
const root = studioRoot();
|
|
5881
|
+
const p = path.resolve(root, String(rel || ''));
|
|
5882
|
+
if (p !== root && !p.startsWith(root + path.sep)) return null;
|
|
5883
|
+
return p;
|
|
5884
|
+
}
|
|
5885
|
+
|
|
5886
|
+
function studioGetSession(folder) {
|
|
5887
|
+
let sid = studioSessions.get(folder);
|
|
5888
|
+
if (sid) {
|
|
5889
|
+
try {
|
|
5890
|
+
const s = engine.cache.get(sid);
|
|
5891
|
+
if (s) return s;
|
|
5892
|
+
} catch {}
|
|
5893
|
+
studioSessions.delete(folder);
|
|
5894
|
+
}
|
|
5895
|
+
const s = engine._load(engine.createSession().id);
|
|
5896
|
+
s.messages = s.messages || [];
|
|
5897
|
+
s.bgTitle = 'Beast Studio'; /* _view.isBg → sohbet geçmişi listesinde gizli */
|
|
5898
|
+
s.bcCode = true; /* todo disiplini + hızlı iş kapanışı (engine) */
|
|
5899
|
+
s.studio = true; /* engine: buildStudioSystem — video yapma/düzenleme ajanı */
|
|
5900
|
+
try {
|
|
5901
|
+
fs.appendFileSync(
|
|
5902
|
+
engine._file(s.id),
|
|
5903
|
+
JSON.stringify({ t: 'meta2', bgOf: '', title: 'Beast Studio', at: new Date().toISOString() }) + '\n'
|
|
5904
|
+
);
|
|
5905
|
+
} catch {}
|
|
5906
|
+
engine.cache.set(s.id, s);
|
|
5907
|
+
studioSessions.set(folder, s.id);
|
|
5908
|
+
return s;
|
|
5909
|
+
}
|
|
5910
|
+
|
|
5911
|
+
/* Studio mesaj kuyruğu: Beast Code kuyruğunun birebir karşılığı —
|
|
5912
|
+
ajan çalışırken mesajlar birikir, iş bitince TEK pakette gönderilir */
|
|
5913
|
+
const ST_DEBOUNCE_MS = 900;
|
|
5914
|
+
const stQueue = new Map(); /* klasör yolu → { timer, msgs[] } */
|
|
5915
|
+
|
|
5916
|
+
function stQueuePush(ws, text, attachments) {
|
|
5917
|
+
let q = stQueue.get(ws);
|
|
5918
|
+
if (!q) {
|
|
5919
|
+
q = { timer: null, msgs: [] };
|
|
5920
|
+
stQueue.set(ws, q);
|
|
5921
|
+
}
|
|
5922
|
+
q.msgs.push({
|
|
5923
|
+
text,
|
|
5924
|
+
attachments: Array.isArray(attachments) && attachments.length ? attachments : undefined,
|
|
5925
|
+
});
|
|
5926
|
+
return q;
|
|
5927
|
+
}
|
|
5928
|
+
|
|
5929
|
+
function stFlush(folder) {
|
|
5930
|
+
const q = stQueue.get(folder);
|
|
5931
|
+
if (!q || !q.msgs.length) return;
|
|
5932
|
+
const s = studioGetSession(folder);
|
|
5933
|
+
if (engine.isBusy(s.id)) return;
|
|
5934
|
+
let merged = '';
|
|
5935
|
+
let mergedAtts = null;
|
|
5936
|
+
for (const m of q.msgs) {
|
|
5937
|
+
if (m.text) merged += (merged ? '\n' : '') + m.text;
|
|
5938
|
+
if (!mergedAtts && Array.isArray(m.attachments) && m.attachments.length) mergedAtts = m.attachments;
|
|
5939
|
+
}
|
|
5940
|
+
if (!merged.trim() && !mergedAtts) {
|
|
5941
|
+
stQueue.delete(folder);
|
|
5942
|
+
clearTimeout(q.timer);
|
|
5943
|
+
return;
|
|
5944
|
+
}
|
|
5945
|
+
s.workspace = folder;
|
|
5946
|
+
s.bcCode = true;
|
|
5947
|
+
s.studio = true;
|
|
5948
|
+
engine.cache.set(s.id, s);
|
|
5949
|
+
const payload = mergedAtts ? { text: merged, attachments: mergedAtts } : merged;
|
|
5950
|
+
if (engine.send(s.id, payload, { userAction: true })) {
|
|
5951
|
+
stQueue.delete(folder);
|
|
5952
|
+
clearTimeout(q.timer);
|
|
5953
|
+
} else {
|
|
5954
|
+
q.retries = (q.retries || 0) + 1;
|
|
5955
|
+
if (q.retries > 5) {
|
|
5956
|
+
stQueue.delete(folder);
|
|
5957
|
+
clearTimeout(q.timer);
|
|
5958
|
+
try {
|
|
5959
|
+
if (win && !win.isDestroyed()) {
|
|
5960
|
+
win.webContents.send('agent:event', { sessionId: s.id, type: 'error', error: 'Beast Studio mesajı gönderilemedi — panele tekrar yaz' });
|
|
5961
|
+
}
|
|
5962
|
+
} catch {}
|
|
5963
|
+
return;
|
|
5964
|
+
}
|
|
5965
|
+
setTimeout(() => { try { stFlush(folder); } catch {} }, 800);
|
|
5966
|
+
}
|
|
5967
|
+
}
|
|
5968
|
+
|
|
5969
|
+
function stFlushOnDone(ev) {
|
|
5970
|
+
if (!ev || (ev.type !== 'done' && ev.type !== 'error') || !ev.sessionId) return;
|
|
5971
|
+
for (const [folder, sid] of studioSessions) {
|
|
5972
|
+
if (String(sid) === String(ev.sessionId) && stQueue.has(folder)) {
|
|
5973
|
+
setTimeout(() => { try { stFlush(folder); } catch {} }, 150);
|
|
5974
|
+
}
|
|
5975
|
+
}
|
|
5976
|
+
}
|
|
5977
|
+
|
|
5978
|
+
ipcMain.handle('studio:send', async (_e, payload) => {
|
|
5979
|
+
const text = String((payload && payload.msg) || '').trim();
|
|
5980
|
+
const attachments = Array.isArray(payload && payload.attachments) ? payload.attachments : [];
|
|
5981
|
+
if (!text && !attachments.length) return { ok: false, error: 'boş mesaj' };
|
|
5982
|
+
if (!engine) return { ok: false, error: 'ajan hazır değil' };
|
|
5983
|
+
const ws = studioRoot();
|
|
5984
|
+
const modeM = /^\/(plan|build|auto)\b/i.exec(text);
|
|
5985
|
+
|
|
5986
|
+
if (!engine.publicState().hasModel) return { ok: false, error: 'model yok — Ayarlar → Provider sekmesinden ekle' };
|
|
5987
|
+
const s = studioGetSession(ws);
|
|
5988
|
+
s.workspace = ws;
|
|
5989
|
+
s.bcCode = true;
|
|
5990
|
+
s.studio = true;
|
|
5991
|
+
engine.cache.set(s.id, s);
|
|
5992
|
+
const busy = engine.isBusy(s.id);
|
|
5993
|
+
if (modeM) {
|
|
5994
|
+
s.bcMode = modeM[1].toLowerCase();
|
|
5995
|
+
if (win && !win.isDestroyed()) {
|
|
5996
|
+
const body = modeM[1].toLowerCase() === 'plan'
|
|
5997
|
+
? 'PLAN MODU — malzemeyi inceler (list_dir/ffprobe), KOMUT ÇALIŞTIRMAZ; adım adım montaj planı verir.'
|
|
5998
|
+
: modeM[1].toLowerCase() === 'build'
|
|
5999
|
+
? 'BUILD MODU — son montaj planını UYGULAR: ffmpeg işlerini çalıştırır, çıktıları doğrular.'
|
|
6000
|
+
: 'OTOMATİK MOD — önce kısa plan, sonra uygulama + doğrulama.';
|
|
6001
|
+
win.webContents.send('agent:event', { sessionId: s.id, type: 'st-mode', mode: s.bcMode, body });
|
|
6002
|
+
}
|
|
6003
|
+
return { ok: true, sessionId: s.id, mode: s.bcMode };
|
|
6004
|
+
}
|
|
6005
|
+
if (busy) {
|
|
6006
|
+
const q = stQueuePush(ws, text, attachments);
|
|
6007
|
+
return { ok: true, queued: true, count: q.msgs.length, sessionId: s.id };
|
|
6008
|
+
}
|
|
6009
|
+
const q = stQueuePush(ws, text, attachments);
|
|
6010
|
+
clearTimeout(q.timer);
|
|
6011
|
+
q.timer = setTimeout(() => { try { stFlush(ws); } catch {} }, ST_DEBOUNCE_MS);
|
|
6012
|
+
return { ok: true, sessionId: s.id, pending: true };
|
|
6013
|
+
});
|
|
6014
|
+
|
|
6015
|
+
ipcMain.handle('studio:stop', async () => {
|
|
6016
|
+
const ws = studioRoot();
|
|
6017
|
+
const sid = studioSessions.get(ws);
|
|
6018
|
+
const q = stQueue.get(ws);
|
|
6019
|
+
if (q) {
|
|
6020
|
+
clearTimeout(q.timer);
|
|
6021
|
+
stQueue.delete(ws);
|
|
6022
|
+
}
|
|
6023
|
+
const wasBusy = sid ? engine.isBusy(sid) : false;
|
|
6024
|
+
let r = false;
|
|
6025
|
+
if (wasBusy) {
|
|
6026
|
+
try { r = engine.interrupt(sid, 'kullanıcı Beast Studio panelinden ■ ile durdurdu'); } catch {}
|
|
6027
|
+
}
|
|
6028
|
+
return { ok: true, wasBusy, interrupted: r };
|
|
6029
|
+
});
|
|
6030
|
+
|
|
6031
|
+
ipcMain.handle('studio:new', async () => {
|
|
6032
|
+
const ws = studioRoot();
|
|
6033
|
+
const sid = studioSessions.get(ws);
|
|
6034
|
+
if (sid && engine.isBusy(sid)) return { ok: false, error: 'mesaj sürüyor — önce ■ ile durdur' };
|
|
6035
|
+
const q = stQueue.get(ws);
|
|
6036
|
+
if (q) {
|
|
6037
|
+
clearTimeout(q.timer);
|
|
6038
|
+
stQueue.delete(ws);
|
|
6039
|
+
}
|
|
6040
|
+
if (sid) {
|
|
6041
|
+
try { engine.deleteSession(sid); } catch {}
|
|
6042
|
+
studioSessions.delete(ws);
|
|
6043
|
+
}
|
|
6044
|
+
return { ok: true };
|
|
6045
|
+
});
|
|
6046
|
+
|
|
6047
|
+
ipcMain.handle('studio:setroot', async () => {
|
|
6048
|
+
try {
|
|
6049
|
+
const r = await dialog.showOpenDialog({
|
|
6050
|
+
title: 'Beast Studio klasörü seç — videolar ve malzemeler bu klasörde',
|
|
6051
|
+
defaultPath: studioRoot(),
|
|
6052
|
+
properties: ['openDirectory'],
|
|
6053
|
+
});
|
|
6054
|
+
if (r.canceled || !r.filePaths || !r.filePaths[0]) return { ok: false, canceled: true };
|
|
6055
|
+
settings.studioRoot = r.filePaths[0];
|
|
6056
|
+
saveSettings();
|
|
6057
|
+
studioWatchStart();
|
|
6058
|
+
return { ok: true, root: settings.studioRoot };
|
|
6059
|
+
} catch (e) {
|
|
6060
|
+
return { ok: false, error: String((e && e.message) || e) };
|
|
6061
|
+
}
|
|
6062
|
+
});
|
|
6063
|
+
|
|
6064
|
+
ipcMain.handle('studio:tree', (_e, rel) => {
|
|
6065
|
+
const p = studioSafe(rel);
|
|
6066
|
+
if (!p) return { ok: false, error: 'geçersiz yol' };
|
|
6067
|
+
try {
|
|
6068
|
+
const entries = fs.readdirSync(p, { withFileTypes: true })
|
|
6069
|
+
.filter((e) => e.name !== 'node_modules' && e.name !== '.git')
|
|
6070
|
+
.map((e) => {
|
|
6071
|
+
let size = 0;
|
|
6072
|
+
try { if (e.isFile()) size = fs.statSync(path.join(p, e.name)).size; } catch {}
|
|
6073
|
+
return { name: e.name, dir: e.isDirectory(), size };
|
|
6074
|
+
})
|
|
6075
|
+
.sort((a, b) => (a.dir === b.dir ? a.name.localeCompare(b.name) : a.dir ? -1 : 1));
|
|
6076
|
+
return { ok: true, workspace: studioRoot(), entries };
|
|
6077
|
+
} catch (e) {
|
|
6078
|
+
return { ok: false, error: String((e && e.message) || e) };
|
|
6079
|
+
}
|
|
6080
|
+
});
|
|
6081
|
+
|
|
6082
|
+
/* Studio video seçici: video bölümündeki "Aç" düğmesi — medya dosyası seçilir,
|
|
6083
|
+
yol düz döner (renderer file:// ile oynatır) */
|
|
6084
|
+
ipcMain.handle('studio:pickvideo', async () => {
|
|
6085
|
+
try {
|
|
6086
|
+
const r = await dialog.showOpenDialog({
|
|
6087
|
+
title: 'Video/ses dosyası seç',
|
|
6088
|
+
defaultPath: studioRoot(),
|
|
6089
|
+
properties: ['openFile'],
|
|
6090
|
+
filters: [
|
|
6091
|
+
{ name: 'Medya', extensions: ['mp4', 'mkv', 'mov', 'avi', 'webm', 'm4v', 'mpg', 'mpeg', 'wmv', 'flv', '3gp', 'ogv', 'mp3', 'wav', 'ogg', 'm4a', 'flac', 'aac', 'opus'] },
|
|
6092
|
+
{ name: 'Tüm dosyalar', extensions: ['*'] },
|
|
6093
|
+
],
|
|
6094
|
+
});
|
|
6095
|
+
if (r.canceled || !r.filePaths || !r.filePaths[0]) return { ok: false, canceled: true };
|
|
6096
|
+
return { ok: true, path: r.filePaths[0] };
|
|
6097
|
+
} catch (e) {
|
|
6098
|
+
return { ok: false, error: String((e && e.message) || e) };
|
|
6099
|
+
}
|
|
6100
|
+
});
|
|
6101
|
+
|
|
6102
|
+
/* Studio ağaç canlı izleme: IDE izleyicisinin karşılığı — studioRoot kökü
|
|
6103
|
+
izlenir, değişim 'ide-tree-changed' ile düşer (renderFileTree moduna göre
|
|
6104
|
+
doğru ağacı tazeler). ffmpeg çıktısı da anında ağaca düşer. */
|
|
6105
|
+
let studioWatcher = null;
|
|
6106
|
+
let studioWatchTimer = null;
|
|
6107
|
+
let studioWatchRoot = '';
|
|
6108
|
+
function studioWatchStart() {
|
|
6109
|
+
const root = studioRoot();
|
|
6110
|
+
if (studioWatcher && studioWatchRoot === root) return;
|
|
6111
|
+
studioWatchStop();
|
|
6112
|
+
if (root === app.getPath('home')) return;
|
|
6113
|
+
try { fs.accessSync(root); } catch { return; }
|
|
6114
|
+
studioWatchRoot = root;
|
|
6115
|
+
try {
|
|
6116
|
+
studioWatcher = fs.watch(root, { recursive: true }, (_evType, fname) => {
|
|
6117
|
+
const f = String(fname || '').replace(/\\/g, '/');
|
|
6118
|
+
if (/^(node_modules|\.git|dist)(\/|$)/i.test(f)) return;
|
|
6119
|
+
if (studioWatchTimer) return;
|
|
6120
|
+
studioWatchTimer = setTimeout(() => {
|
|
6121
|
+
studioWatchTimer = null;
|
|
6122
|
+
try {
|
|
6123
|
+
if (win && !win.isDestroyed()) {
|
|
6124
|
+
win.webContents.send('agent:event', { type: 'ide-tree-changed' });
|
|
6125
|
+
}
|
|
6126
|
+
} catch {}
|
|
6127
|
+
}, 500);
|
|
6128
|
+
});
|
|
6129
|
+
studioWatcher.on('error', () => {
|
|
6130
|
+
studioWatchStop();
|
|
6131
|
+
setTimeout(() => { try { studioWatchStart(); } catch {} }, 3000);
|
|
6132
|
+
});
|
|
6133
|
+
} catch {}
|
|
6134
|
+
}
|
|
6135
|
+
function studioWatchStop() {
|
|
6136
|
+
if (studioWatcher) { try { studioWatcher.close(); } catch {} }
|
|
6137
|
+
studioWatcher = null;
|
|
6138
|
+
studioWatchRoot = '';
|
|
6139
|
+
if (studioWatchTimer) { clearTimeout(studioWatchTimer); studioWatchTimer = null; }
|
|
6140
|
+
}
|
|
6141
|
+
|
|
5860
6142
|
/* düşünme (reasoning) seviyesi */
|
|
5861
6143
|
ipcMain.handle('think:set', (_e, v) => {
|
|
5862
6144
|
setThinkLevel(v);
|
|
5863
6145
|
return engine.publicState();
|
|
5864
6146
|
});
|
|
5865
6147
|
|
|
6148
|
+
/* ---------- PANO KÖPRÜSÜ (uygulama geneli sağ tık menüsü) ----------
|
|
6149
|
+
Chromium rendererdaki execCommand('paste') güvenilir değil; Kes/Kopyala/
|
|
6150
|
+
Yapıştır'ı pano köprüsüyle MANUEL yapıyoruz (imleç konumunda ekleme). */
|
|
6151
|
+
ipcMain.handle('clip:read', () => {
|
|
6152
|
+
try { return clipboard.readText(); } catch { return ''; }
|
|
6153
|
+
});
|
|
6154
|
+
ipcMain.handle('clip:write', (_e, t) => {
|
|
6155
|
+
try { clipboard.writeText(String(t ?? '')); return { ok: true }; } catch (e) {
|
|
6156
|
+
return { ok: false, error: String((e && e.message) || e) };
|
|
6157
|
+
}
|
|
6158
|
+
});
|
|
6159
|
+
|
|
5866
6160
|
|
|
5867
6161
|
/* Arama zinciri sırası (Ayarlar → Web Arama'dan değiştirilir) */
|
|
5868
6162
|
ipcMain.handle('searchorder:get', () => ({ chain: toolsMod.getSearchChain() }));
|
|
@@ -5979,10 +6273,32 @@ ipcMain.handle('store:export', (_e, id) => storeMod.exportEntry(id));
|
|
|
5979
6273
|
|
|
5980
6274
|
/* ---------- IDE MODU (sol: dosya gezgini · orta: chat · sağ: preview) ---------- */
|
|
5981
6275
|
|
|
6276
|
+
/* opencode disiplini: editör TÜM kod altyapısını açar — web-only değil.
|
|
6277
|
+
İkili (binary) koruması uzantı listesiyle değil, okuma anındaki NUL-byte
|
|
6278
|
+
sniffing ile yapılır (ide:read). Liste yalnız bilinen metin türlerini
|
|
6279
|
+
hızlandırır; bilinmeyen uzantılar da denenir. */
|
|
5982
6280
|
const IDE_TEXT_EXT = new Set([
|
|
5983
|
-
'.html', '.htm', '.css', '.
|
|
5984
|
-
'.
|
|
5985
|
-
'.
|
|
6281
|
+
'.html', '.htm', '.css', '.scss', '.sass', '.less', '.js', '.mjs', '.cjs', '.ts',
|
|
6282
|
+
'.tsx', '.jsx', '.mts', '.cts', '.json', '.jsonc', '.json5', '.md', '.mdx', '.txt',
|
|
6283
|
+
'.csv', '.tsv', '.log', '.py', '.pyi', '.pyw', '.ps1', '.psm1', '.bat', '.cmd',
|
|
6284
|
+
'.sh', '.bash', '.zsh', '.yaml', '.yml', '.toml', '.xml', '.ini', '.cfg', '.conf',
|
|
6285
|
+
'.env', '.properties', '.svg', '.gitignore', '.gitattributes', '.editorconfig',
|
|
6286
|
+
'.npmrc', '.nvmrc', '.babelrc', '.prettierrc', '.eslintrc', '.go', '.mod', '.sum',
|
|
6287
|
+
'.rs', '.java', '.kt', '.kts', '.swift', '.rb', '.erb', '.php', '.cs', '.csproj',
|
|
6288
|
+
'.sln', '.vb', '.fs', '.c', '.h', '.cpp', '.hpp', '.cc', '.hh', '.cxx', '.hxx',
|
|
6289
|
+
'.m', '.mm', '.sql', '.prisma', '.graphql', '.gql', '.proto', '.vue', '.svelte',
|
|
6290
|
+
'.astro', '.lua', '.pl', '.pm', '.r', '.R', '.jl', '.dart', '.scala', '.groovy',
|
|
6291
|
+
'.gradle', '.cmake', '.mk', '.make', '.dockerfile', '.terraform', '.tf', '.hcl',
|
|
6292
|
+
'.asm', '.s', '.vbs', '.reg', '.htaccess', '.lock', '.patch', '.diff', '.ipynb',
|
|
6293
|
+
]);
|
|
6294
|
+
const IDE_BINARY_EXT = new Set([
|
|
6295
|
+
'.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp', '.ico', '.icns', '.tiff',
|
|
6296
|
+
'.mp4', '.mkv', '.mov', '.avi', '.webm', '.m4v', '.mpg', '.mpeg', '.wmv', '.flv',
|
|
6297
|
+
'.3gp', '.ogv', '.mp3', '.wav', '.ogg', '.m4a', '.flac', '.aac', '.opus', '.wma',
|
|
6298
|
+
'.zip', '.7z', '.rar', '.gz', '.tar', '.bz2', '.xz', '.exe', '.dll', '.so',
|
|
6299
|
+
'.dylib', '.bin', '.dat', '.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt',
|
|
6300
|
+
'.pptx', '.ttf', '.otf', '.woff', '.woff2', '.eot', '.db', '.sqlite', '.pyc',
|
|
6301
|
+
'.class', '.jar', '.wasm', '.psd', '.ai', '.prproj', '.aep', '.fcpxml',
|
|
5986
6302
|
]);
|
|
5987
6303
|
const IDE_MAX_BYTES = 400 * 1024;
|
|
5988
6304
|
|
|
@@ -6088,7 +6404,7 @@ ipcMain.handle('ide:read', (_e, rel) => {
|
|
|
6088
6404
|
const p = ideSafe(rel);
|
|
6089
6405
|
if (!p) return { ok: false, error: 'geçersiz yol' };
|
|
6090
6406
|
const ext = path.extname(p).toLowerCase();
|
|
6091
|
-
if (ext &&
|
|
6407
|
+
if (ext && IDE_BINARY_EXT.has(ext)) return { ok: false, error: 'ikili (binary) dosya — düzenlenemez' };
|
|
6092
6408
|
try {
|
|
6093
6409
|
const st = fs.statSync(p);
|
|
6094
6410
|
if (st.isDirectory()) return { ok: false, error: 'klasör' };
|
|
@@ -6105,7 +6421,7 @@ ipcMain.handle('ide:write', (_e, p) => {
|
|
|
6105
6421
|
const target = ideSafe(p && p.rel);
|
|
6106
6422
|
if (!target) return { ok: false, error: 'geçersiz yol' };
|
|
6107
6423
|
const ext = path.extname(target).toLowerCase();
|
|
6108
|
-
if (ext &&
|
|
6424
|
+
if (ext && IDE_BINARY_EXT.has(ext)) return { ok: false, error: 'ikili (binary) dosya — yazılamaz' };
|
|
6109
6425
|
try {
|
|
6110
6426
|
const body = String((p && p.content) ?? '');
|
|
6111
6427
|
if (Buffer.byteLength(body, 'utf8') > IDE_MAX_BYTES) return { ok: false, error: 'içerik çok büyük (max 400KB)' };
|
|
@@ -6142,6 +6458,32 @@ ipcMain.handle('ide:delete', async (_e, rel) => {
|
|
|
6142
6458
|
}
|
|
6143
6459
|
});
|
|
6144
6460
|
|
|
6461
|
+
/* Studio silme: IDE kökünden BAĞIMSIZ studioRoot üzerinde çalışır */
|
|
6462
|
+
ipcMain.handle('studio:delete', async (_e, rel) => {
|
|
6463
|
+
const p = studioSafe(rel);
|
|
6464
|
+
if (!p || p === studioRoot()) return { ok: false, error: 'geçersiz yol' };
|
|
6465
|
+
try {
|
|
6466
|
+
const st = fs.statSync(p);
|
|
6467
|
+
const isDir = st.isDirectory();
|
|
6468
|
+
const owner = win && !win.isDestroyed() ? win : undefined;
|
|
6469
|
+
const r = owner
|
|
6470
|
+
? await dialog.showMessageBox(owner, {
|
|
6471
|
+
type: 'warning',
|
|
6472
|
+
title: 'Sil',
|
|
6473
|
+
message: `"${rel}" ${isDir ? 'klasörünü (içi dahil)' : 'dosyasını'} silmek istiyor musun?`,
|
|
6474
|
+
buttons: ['Sil', 'Vazgeç'],
|
|
6475
|
+
defaultId: 1,
|
|
6476
|
+
cancelId: 1,
|
|
6477
|
+
})
|
|
6478
|
+
: { response: 1 };
|
|
6479
|
+
if (r.response !== 0) return { ok: false, canceled: true };
|
|
6480
|
+
fs.rmSync(p, { recursive: true, force: true });
|
|
6481
|
+
return { ok: true };
|
|
6482
|
+
} catch (e) {
|
|
6483
|
+
return { ok: false, error: String((e && e.message) || e) };
|
|
6484
|
+
}
|
|
6485
|
+
});
|
|
6486
|
+
|
|
6145
6487
|
/* Sağ tık menüsü: HTML dosyasını dahili tarayıcıda GÖRÜNÜR aç */
|
|
6146
6488
|
/* ---------- BC DAHİLİ STATİK SUNUCU ----------
|
|
6147
6489
|
Beast Code çıktısı ASLA file:// ile açılmaz: ES modülleri, fetch, ServiceWorker
|
package/src/preload.js
CHANGED
|
@@ -77,6 +77,15 @@ contextBridge.exposeInMainWorld('beast', {
|
|
|
77
77
|
bcUndo: (sessionId, todoId) => ipcRenderer.invoke('bc:undo', { sessionId, todoId }),
|
|
78
78
|
beastcodeStop: () => ipcRenderer.invoke('beastcode:stop'),
|
|
79
79
|
beastcodeNew: () => ipcRenderer.invoke('beastcode:new'),
|
|
80
|
+
studioSend: (msg, attachments) => ipcRenderer.invoke('studio:send', { msg, attachments }),
|
|
81
|
+
studioStop: () => ipcRenderer.invoke('studio:stop'),
|
|
82
|
+
studioNew: () => ipcRenderer.invoke('studio:new'),
|
|
83
|
+
studioSetRoot: () => ipcRenderer.invoke('studio:setroot'),
|
|
84
|
+
studioTree: (rel) => ipcRenderer.invoke('studio:tree', rel),
|
|
85
|
+
studioPickVideo: () => ipcRenderer.invoke('studio:pickvideo'),
|
|
86
|
+
studioDelete: (rel) => ipcRenderer.invoke('studio:delete', rel),
|
|
87
|
+
clipRead: () => ipcRenderer.invoke('clip:read'),
|
|
88
|
+
clipWrite: (t) => ipcRenderer.invoke('clip:write', t),
|
|
80
89
|
thinkSet: (v) => ipcRenderer.invoke('think:set', v),
|
|
81
90
|
searchOrderGet: () => ipcRenderer.invoke('searchorder:get'),
|
|
82
91
|
searchOrderSet: (chain) => ipcRenderer.invoke('searchorder:set', chain),
|
|
@@ -1,73 +1,73 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/* Dahili tarayıcı preload — Google girişinin "bu tarayıcı veya uygulama
|
|
4
|
-
güvenli olmayabilir" engelini aşar. Electron'da eksik olan gerçek Chrome
|
|
5
|
-
imzaları (window.chrome, navigator.plugins/mimeTypes/languages, webdriver)
|
|
6
|
-
ana dünyada (main world) tamamlanır. Her belge yüklemesinde tekrar koşar. */
|
|
7
|
-
|
|
8
|
-
const { webFrame } = require('electron');
|
|
9
|
-
|
|
10
|
-
const PATCH = `(function () {
|
|
11
|
-
try {
|
|
12
|
-
try { Object.defineProperty(navigator, 'webdriver', { get: function () { return false; } }); } catch (e) {}
|
|
13
|
-
try { Object.defineProperty(navigator, 'languages', { get: function () { return ['tr-TR', 'tr', 'en-US', 'en']; } }); } catch (e) {}
|
|
14
|
-
|
|
15
|
-
if (!window.chrome) { try { window.chrome = {}; } catch (e) {} }
|
|
16
|
-
if (!window.chrome.app) {
|
|
17
|
-
window.chrome.app = {
|
|
18
|
-
isInstalled: false,
|
|
19
|
-
InstallState: { DISABLED: 'disabled', INSTALLED: 'installed', NOT_INSTALLED: 'not_installed' },
|
|
20
|
-
RunningState: { CANNOT_RUN: 'cannot_run', READY_TO_RUN: 'ready_to_run', RUNNING: 'running' },
|
|
21
|
-
getDetails: function () {},
|
|
22
|
-
installState: function () { return 'not_installed'; },
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
if (!window.chrome.runtime) {
|
|
26
|
-
window.chrome.runtime = {
|
|
27
|
-
PlatformOs: { MAC: 'mac', WIN: 'win', ANDROID: 'android', CROS: 'cros', LINUX: 'linux', OPENBSD: 'openbsd' },
|
|
28
|
-
PlatformArch: { ARM: 'arm', X86_32: 'x86-32', X86_64: 'x86-64', MIPS: 'mips', MIPS64: 'mips64' },
|
|
29
|
-
PlatformNaclArch: { ARM: 'arm', X86_32: 'x86-32', X86_64: 'x86-64', MIPS: 'mips', MIPS64: 'mips64' },
|
|
30
|
-
RequestUpdateCheckStatus: { NO_UPDATE: 'no_update', THROTTLED: 'throttled', UPDATE_AVAILABLE: 'update_available' },
|
|
31
|
-
OnInstalledReason: { CHROME_UPDATE: 'chrome_update', INSTALL: 'install', SHARED_MODULE_UPDATE: 'shared_module_update', UPDATE: 'update' },
|
|
32
|
-
OnRestartRequiredReason: { APP_UPDATE: 'app_update', OS_UPDATE: 'os_update', PERIODIC: 'periodic' },
|
|
33
|
-
connect: function () {},
|
|
34
|
-
sendMessage: function () {},
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (navigator.plugins && navigator.plugins.length === 0) {
|
|
39
|
-
var pluginData = [
|
|
40
|
-
{ name: 'PDF Viewer', description: 'Portable Document Format', filename: 'internal-pdf-viewer', type: 'application/pdf', suffixes: 'pdf' },
|
|
41
|
-
{ name: 'Chrome PDF Viewer', description: 'Portable Document Format', filename: 'internal-pdf-viewer', type: 'application/pdf', suffixes: 'pdf' },
|
|
42
|
-
{ name: 'Chromium PDF Viewer', description: 'Portable Document Format', filename: 'internal-pdf-viewer', type: 'application/pdf', suffixes: 'pdf' },
|
|
43
|
-
{ name: 'Microsoft Edge PDF Viewer', description: 'Portable Document Format', filename: 'internal-pdf-viewer', type: 'application/pdf', suffixes: 'pdf' },
|
|
44
|
-
{ name: 'WebKit built-in PDF', description: 'Portable Document Format', filename: 'internal-pdf-viewer', type: 'application/pdf', suffixes: 'pdf' },
|
|
45
|
-
];
|
|
46
|
-
var plugins = {
|
|
47
|
-
length: pluginData.length,
|
|
48
|
-
item: function (i) { return this[i] || null; },
|
|
49
|
-
namedItem: function (n) { for (var k in this) { if (this[k] && this[k].name === n) return this[k]; } return null; },
|
|
50
|
-
refresh: function () {},
|
|
51
|
-
};
|
|
52
|
-
pluginData.forEach(function (p, i) {
|
|
53
|
-
var pl = { name: p.name, description: p.description, filename: p.filename, length: 1 };
|
|
54
|
-
pl[0] = { type: p.type, suffixes: p.suffixes, description: p.description, enabledPlugin: pl };
|
|
55
|
-
plugins[i] = pl;
|
|
56
|
-
});
|
|
57
|
-
try { Object.defineProperty(navigator, 'plugins', { get: function () { return plugins; } }); } catch (e) {}
|
|
58
|
-
try {
|
|
59
|
-
var mimes = {
|
|
60
|
-
length: pluginData.length,
|
|
61
|
-
item: function (i) { return (plugins[i] && plugins[i][0]) || null; },
|
|
62
|
-
namedItem: function (n) { for (var k in this) { if (this[k] && this[k].type === n) return this[k]; } return null; },
|
|
63
|
-
};
|
|
64
|
-
pluginData.forEach(function (p, i) { mimes[p.type] = plugins[i] && plugins[i][0]; });
|
|
65
|
-
Object.defineProperty(navigator, 'mimeTypes', { get: function () { return mimes; } });
|
|
66
|
-
} catch (e) {}
|
|
67
|
-
}
|
|
68
|
-
} catch (e) {}
|
|
69
|
-
})();`;
|
|
70
|
-
|
|
71
|
-
try {
|
|
72
|
-
webFrame.executeJavaScript(PATCH).catch(() => {});
|
|
73
|
-
} catch {}
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/* Dahili tarayıcı preload — Google girişinin "bu tarayıcı veya uygulama
|
|
4
|
+
güvenli olmayabilir" engelini aşar. Electron'da eksik olan gerçek Chrome
|
|
5
|
+
imzaları (window.chrome, navigator.plugins/mimeTypes/languages, webdriver)
|
|
6
|
+
ana dünyada (main world) tamamlanır. Her belge yüklemesinde tekrar koşar. */
|
|
7
|
+
|
|
8
|
+
const { webFrame } = require('electron');
|
|
9
|
+
|
|
10
|
+
const PATCH = `(function () {
|
|
11
|
+
try {
|
|
12
|
+
try { Object.defineProperty(navigator, 'webdriver', { get: function () { return false; } }); } catch (e) {}
|
|
13
|
+
try { Object.defineProperty(navigator, 'languages', { get: function () { return ['tr-TR', 'tr', 'en-US', 'en']; } }); } catch (e) {}
|
|
14
|
+
|
|
15
|
+
if (!window.chrome) { try { window.chrome = {}; } catch (e) {} }
|
|
16
|
+
if (!window.chrome.app) {
|
|
17
|
+
window.chrome.app = {
|
|
18
|
+
isInstalled: false,
|
|
19
|
+
InstallState: { DISABLED: 'disabled', INSTALLED: 'installed', NOT_INSTALLED: 'not_installed' },
|
|
20
|
+
RunningState: { CANNOT_RUN: 'cannot_run', READY_TO_RUN: 'ready_to_run', RUNNING: 'running' },
|
|
21
|
+
getDetails: function () {},
|
|
22
|
+
installState: function () { return 'not_installed'; },
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
if (!window.chrome.runtime) {
|
|
26
|
+
window.chrome.runtime = {
|
|
27
|
+
PlatformOs: { MAC: 'mac', WIN: 'win', ANDROID: 'android', CROS: 'cros', LINUX: 'linux', OPENBSD: 'openbsd' },
|
|
28
|
+
PlatformArch: { ARM: 'arm', X86_32: 'x86-32', X86_64: 'x86-64', MIPS: 'mips', MIPS64: 'mips64' },
|
|
29
|
+
PlatformNaclArch: { ARM: 'arm', X86_32: 'x86-32', X86_64: 'x86-64', MIPS: 'mips', MIPS64: 'mips64' },
|
|
30
|
+
RequestUpdateCheckStatus: { NO_UPDATE: 'no_update', THROTTLED: 'throttled', UPDATE_AVAILABLE: 'update_available' },
|
|
31
|
+
OnInstalledReason: { CHROME_UPDATE: 'chrome_update', INSTALL: 'install', SHARED_MODULE_UPDATE: 'shared_module_update', UPDATE: 'update' },
|
|
32
|
+
OnRestartRequiredReason: { APP_UPDATE: 'app_update', OS_UPDATE: 'os_update', PERIODIC: 'periodic' },
|
|
33
|
+
connect: function () {},
|
|
34
|
+
sendMessage: function () {},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (navigator.plugins && navigator.plugins.length === 0) {
|
|
39
|
+
var pluginData = [
|
|
40
|
+
{ name: 'PDF Viewer', description: 'Portable Document Format', filename: 'internal-pdf-viewer', type: 'application/pdf', suffixes: 'pdf' },
|
|
41
|
+
{ name: 'Chrome PDF Viewer', description: 'Portable Document Format', filename: 'internal-pdf-viewer', type: 'application/pdf', suffixes: 'pdf' },
|
|
42
|
+
{ name: 'Chromium PDF Viewer', description: 'Portable Document Format', filename: 'internal-pdf-viewer', type: 'application/pdf', suffixes: 'pdf' },
|
|
43
|
+
{ name: 'Microsoft Edge PDF Viewer', description: 'Portable Document Format', filename: 'internal-pdf-viewer', type: 'application/pdf', suffixes: 'pdf' },
|
|
44
|
+
{ name: 'WebKit built-in PDF', description: 'Portable Document Format', filename: 'internal-pdf-viewer', type: 'application/pdf', suffixes: 'pdf' },
|
|
45
|
+
];
|
|
46
|
+
var plugins = {
|
|
47
|
+
length: pluginData.length,
|
|
48
|
+
item: function (i) { return this[i] || null; },
|
|
49
|
+
namedItem: function (n) { for (var k in this) { if (this[k] && this[k].name === n) return this[k]; } return null; },
|
|
50
|
+
refresh: function () {},
|
|
51
|
+
};
|
|
52
|
+
pluginData.forEach(function (p, i) {
|
|
53
|
+
var pl = { name: p.name, description: p.description, filename: p.filename, length: 1 };
|
|
54
|
+
pl[0] = { type: p.type, suffixes: p.suffixes, description: p.description, enabledPlugin: pl };
|
|
55
|
+
plugins[i] = pl;
|
|
56
|
+
});
|
|
57
|
+
try { Object.defineProperty(navigator, 'plugins', { get: function () { return plugins; } }); } catch (e) {}
|
|
58
|
+
try {
|
|
59
|
+
var mimes = {
|
|
60
|
+
length: pluginData.length,
|
|
61
|
+
item: function (i) { return (plugins[i] && plugins[i][0]) || null; },
|
|
62
|
+
namedItem: function (n) { for (var k in this) { if (this[k] && this[k].type === n) return this[k]; } return null; },
|
|
63
|
+
};
|
|
64
|
+
pluginData.forEach(function (p, i) { mimes[p.type] = plugins[i] && plugins[i][0]; });
|
|
65
|
+
Object.defineProperty(navigator, 'mimeTypes', { get: function () { return mimes; } });
|
|
66
|
+
} catch (e) {}
|
|
67
|
+
}
|
|
68
|
+
} catch (e) {}
|
|
69
|
+
})();`;
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
webFrame.executeJavaScript(PATCH).catch(() => {});
|
|
73
|
+
} catch {}
|
package/src/renderer/i18n.js
CHANGED
|
@@ -512,6 +512,7 @@
|
|
|
512
512
|
tipStore: 'Skills Store',
|
|
513
513
|
tipGithub: 'GitHub Trending — repoları favorile, chate at, ajana araştır',
|
|
514
514
|
tipIde: 'IDE Modu — dosyalar + chat + preview',
|
|
515
|
+
tipStudio: 'Beast Studio — video yapma/düzenleme modu',
|
|
515
516
|
tipCodeHide: 'Editörü kapat',
|
|
516
517
|
tipCodeShow: 'Editörü göster',
|
|
517
518
|
ide_files: 'DOSYALAR',
|
|
@@ -522,6 +523,7 @@
|
|
|
522
523
|
ide_preview: '▶ Preview',
|
|
523
524
|
ide_saved: 'Kaydedildi — preview\u2019ı yenile',
|
|
524
525
|
bc_ph: 'Beast Code mesajı yaz — Enter ile gönder…',
|
|
526
|
+
st_ph: 'Beast Studio mesajı yaz — Enter ile gönder…',
|
|
525
527
|
bc_new: 'Yeni oturum',
|
|
526
528
|
bc_clear: 'Çıktıyı temizle',
|
|
527
529
|
bc_stop: 'Çalışan mesajı durdur',
|
|
@@ -1131,6 +1133,7 @@
|
|
|
1131
1133
|
tipStore: 'Skills Store',
|
|
1132
1134
|
tipGithub: 'GitHub Trending — favorite repos, send to chat, ask the agent',
|
|
1133
1135
|
tipIde: 'IDE Mode — files + chat + preview',
|
|
1136
|
+
tipStudio: 'Beast Studio — video making/editing mode',
|
|
1134
1137
|
tipCodeHide: 'Hide editor',
|
|
1135
1138
|
tipCodeShow: 'Show editor',
|
|
1136
1139
|
ide_files: 'FILES',
|
|
@@ -1141,6 +1144,7 @@
|
|
|
1141
1144
|
ide_preview: '▶ Preview',
|
|
1142
1145
|
ide_saved: 'Saved — refresh the preview',
|
|
1143
1146
|
bc_ph: 'Type a message for Beast Code — press Enter to send…',
|
|
1147
|
+
st_ph: 'Type a message for Beast Studio — press Enter to send…',
|
|
1144
1148
|
bc_new: 'New session',
|
|
1145
1149
|
bc_clear: 'Clear output',
|
|
1146
1150
|
bc_stop: 'Stop running message',
|