beast-agent 2.30.6 → 2.32.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/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, clipboard } = require('electron');
3
+ const { app, BrowserWindow, WebContentsView, ipcMain, shell, dialog, Tray, Menu, nativeImage, desktopCapturer, session, net: electronNet, clipboard, Notification } = require('electron');
4
4
  const path = require('path');
5
5
  const fs = require('fs');
6
6
  const http = require('http');
@@ -26,10 +26,15 @@ const watchers = require('./agent/watchers');
26
26
  const usageMod = require('./agent/usage');
27
27
  const bus = require('./agent/bus');
28
28
  const computeruse = require('./agent/computeruse');
29
+ const fsguard = require('./agent/fsguard');
29
30
  const log = require('./agent/logger');
30
31
  const headroom = require('./agent/headroom');
31
32
  const QRCode = require('qrcode'); /* Expo Go QR (bc-expurl) — whatsapp ile aynı paket */
32
33
 
34
+ /* Renderer'a sır gönderirken kullanılan maske; kaydederken aynen geri gelirse
35
+ gerçek değer korunur (anahtarlar UI'da düz metin dolaşmaz). */
36
+ const SECRET_MASK = '***';
37
+
33
38
  /* #3 otomatik updater: sessiz — indirir, kapanışta kurar, kullanıcıya soru sormaz.
34
39
  Paketlenmemiş (npm start) modda devre dışı; Update sekmesi ve /update komutu kontrol eder. */
35
40
  let autoUpdater = null;
@@ -267,6 +272,8 @@ try {
267
272
  try { nodemailer = require('nodemailer'); } catch {}
268
273
 
269
274
  const APP_DIR = path.join(app.getPath('appData'), 'beast');
275
+ /* Windows toast bildirimleri için AppUserModelID erken ayarlanmalı */
276
+ try { app.setAppUserModelId('com.quantumalgo.beastagent'); } catch {}
270
277
  /* mem0-native embedding modeli whisper ile AYNI cache'i kullanır (src/agent/mem0.js okur) */
271
278
  process.env.BEAST_MODELS_DIR = path.join(APP_DIR, 'models');
272
279
 
@@ -296,6 +303,8 @@ for (const d of [APP_DIR, SESSIONS_DIR]) fs.mkdirSync(d, { recursive: true });
296
303
 
297
304
  let win = null;
298
305
  let engine = null;
306
+ /* Paralel ajan durum takibi: bitişte BİR kez toast bildirimi (spam yok) */
307
+ const bgNotifSeen = new Map(); // sid -> son görülen durum
299
308
  let settings = loadSettings();
300
309
  ensureBeastCode();
301
310
  startHealthServer(); /* splash/boot aşamasından itibaren /health ayakta */
@@ -2337,20 +2346,34 @@ async function handleWaIncoming(jid, payload, senderNum) {
2337
2346
  if (!engine) return;
2338
2347
  if (typeof payload === 'string') payload = { text: payload };
2339
2348
  const isGroup = !!payload.isGroup || jid.endsWith('@g.us');
2349
+ /* İzin listesi kaydı — grup kapıları ve doğal dil komutları bunu kullanır:
2350
+ izinli değilse yalnız sohbet; agentic iş yaptıramaz. */
2351
+ const senderHit = waFind(senderNum);
2340
2352
 
2341
- /* slash komutları: DM'de her zaman; grupta sadece bot mention edildiyse */
2353
+ /* slash komutları: DM'de her zaman; grupta sadece bot mention edildiyse.
2354
+ GRUP KAPISI: izin listesinde olmayan üye slash (agentic) komut kullanamaz —
2355
+ herkes sohbet eder ama işi yalnız izinli kişiler yaptırır. */
2342
2356
  const txt0 = String(payload.text || '').trim();
2343
2357
  if (txt0.startsWith('/') && !txt0.includes('\n') && (!isGroup || payload.mentioned)) {
2358
+ if (isGroup && !senderHit && !/^\/(help|version)\b/i.test(txt0)) {
2359
+ await sendWaSafe(
2360
+ jid,
2361
+ 'Bu komutu yalnız izinli kişiler kullanabilir. Sohbet için beni @mention ile çağırabilirsin.'
2362
+ ).catch(() => {});
2363
+ return;
2364
+ }
2344
2365
  if (await tryWaSlash(jid, txt0, senderNum, payload)) return;
2345
2366
  }
2346
2367
 
2347
2368
  /* "hepsini sil" / "tüm cron" / "cronları temizle" gibi doğal dil → cron temizle.
2369
+ Yalnız İZİNLİ gönderen: misafir grup üyesi cron silemez.
2348
2370
  clearAll() bellekteki job'ları VE diski boşaltır, zamanlayıcıyı durdurur
2349
2371
  (sadece dosya silmek yetmezdi: çalışan proses belleği geri yazıyordu). */
2350
2372
  const lc = txt0.toLowerCase();
2351
2373
  if (
2352
- (lc.includes('cron') && /(sil|temizle|kaldır|hepsi|tüm|sıfırla)/.test(lc)) ||
2353
- /^(hepsini|hepsını|tümünü|tümünü|hepsnı|hepsnı)\b.*(sil|sıl|temizle|kaldır)/.test(lc)
2374
+ senderHit &&
2375
+ ((lc.includes('cron') && /(sil|temizle|kaldır|hepsi|tüm|sıfırla)/.test(lc)) ||
2376
+ /^(hepsini|hepsını|tümünü|tümünü|hepsnı|hepsnı)\b.*(sil|sıl|temizle|kaldır)/.test(lc))
2354
2377
  ) {
2355
2378
  const r = cron.clearAll();
2356
2379
  cronEmit();
@@ -2364,8 +2387,10 @@ async function handleWaIncoming(jid, payload, senderNum) {
2364
2387
  }
2365
2388
 
2366
2389
  /* "hatırlatmaları sil" / "alarm temizle" / "reminders kaldır" → yalnızca
2367
- otomatik hatırlatmalar silinir (cron görevlerine dokunulmaz). */
2390
+ otomatik hatırlatmalar silinir (cron görevlerine dokunulmaz).
2391
+ Yalnız İZİNLİ gönderen tetikleyebilir. */
2368
2392
  if (
2393
+ senderHit &&
2369
2394
  /hat[iı]rlat|alarm|remind/.test(lc) &&
2370
2395
  /(sil|sıl|temizle|kaldır|iptal|sıfırla|kapat)/.test(lc)
2371
2396
  ) {
@@ -2505,7 +2530,10 @@ async function mqueueTick() {
2505
2530
 
2506
2531
  async function processWaMessage(jid, payload, senderNum, requeues = 0) {
2507
2532
  const isGroup = !!payload.isGroup;
2508
- const hit = isGroup ? null : waFind(senderNum);
2533
+ /* GRUP: gönderen izin listesinde mi? İzinli → kendi yetki seviyesi;
2534
+ değilse 'chat' (sohbet serbest, araç YOK — agentic işi yalnız izinliler
2535
+ yaptırabilir). DM'de izinsiz gönderen buraya gelemez. */
2536
+ const hit = waFind(senderNum);
2509
2537
  if (!isGroup && !hit) {
2510
2538
  waLog(`skip flush: izinli eşleşme yok (sender=+${senderNum || '?'})`);
2511
2539
  return;
@@ -2522,8 +2550,8 @@ async function processWaMessage(jid, payload, senderNum, requeues = 0) {
2522
2550
  waLastActiveJid = String(jid); // cevap/dosya hedefi: en son yazan sohbet
2523
2551
  // Cevap verilecek — karşı telefonda "yazıyor…" göstergesi (medya işlenene dek sürer)
2524
2552
  wa.setComposing(jid, true);
2525
- // Kişi bazlı granül izin: all/web/read/chat
2526
- let perm = isGroup ? 'all' : hit.perm || (hit.lockdown ? 'chat' : 'all');
2553
+ // Kişi bazlı granül izin: all/web/read/chat — grup misafiri varsayılan 'chat'
2554
+ let perm = hit ? hit.perm || (hit.lockdown ? 'chat' : 'all') : 'chat';
2527
2555
  engine.setSessionPerm(sid, perm);
2528
2556
 
2529
2557
  /* BOT SİSTEMİ: numara → bot eşleştirme (izinli kayıtta bot_id yoksa beast'e düşer) */
@@ -3208,6 +3236,17 @@ function reloadBackend() {
3208
3236
  },
3209
3237
  emit: (ev) => {
3210
3238
  if (win && !win.isDestroyed()) win.webContents.send('agent:event', ev);
3239
+ /* PARALEL AJAN BİTİŞİ: running → done/error/aborted geçişinde tek toast */
3240
+ if (ev.type === 'agents' && Array.isArray(ev.jobs)) {
3241
+ for (const j of ev.jobs) {
3242
+ const prev = bgNotifSeen.get(j.id);
3243
+ bgNotifSeen.set(j.id, j.status);
3244
+ if (prev === 'running' && j.status && j.status !== 'running' && j.status !== 'queued' && !j.continuous) {
3245
+ const durum = j.status === 'done' ? 'tamamlandı' : j.status === 'error' ? 'HATA ile bitti' : 'iptal edildi';
3246
+ toastNotify(`Ajan: ${j.title || j.code || 'görev'}`, durum, 'agents');
3247
+ }
3248
+ }
3249
+ }
3211
3250
  /* opencode permission (BC): ask kanalı UI'a zaten yukarıda düştü — WA
3212
3251
  sahibine de tek satır bildir gitsin (/approve · /approve always · /deny) */
3213
3252
  if (ev.type === 'permission.asked' && ev.request && wa && wa.connected) {
@@ -3717,6 +3756,30 @@ function showWin() {
3717
3756
  }
3718
3757
  }
3719
3758
 
3759
+ /* ---------- Windows toast bildirimi ----------
3760
+ watcher/cron/paralel ajan bitişinde sistem bildirimi; tıklanınca pencere
3761
+ açılır ve renderer'a notify-click eventi gider (ilgili panel açılır).
3762
+ Ayarlardan kapatılabilir: settings.notifyToast === false */
3763
+ function toastNotify(title, body, target) {
3764
+ try {
3765
+ if (settings.notifyToast === false) return;
3766
+ if (typeof Notification === 'undefined' || !Notification.isSupported()) return;
3767
+ const n = new Notification({
3768
+ title: String(title || 'Beast Agent').slice(0, 120),
3769
+ body: String(body || '').slice(0, 240),
3770
+ icon: path.join(__dirname, '..', 'assets', 'tray.png'),
3771
+ silent: false,
3772
+ });
3773
+ n.on('click', () => {
3774
+ showWin();
3775
+ if (win && !win.isDestroyed()) {
3776
+ win.webContents.send('agent:event', { type: 'notify-click', target: String(target || '') });
3777
+ }
3778
+ });
3779
+ n.show();
3780
+ } catch {}
3781
+ }
3782
+
3720
3783
  /* ---------------- dahili tarayıcı ---------------- */
3721
3784
 
3722
3785
  const BROWSER_TOOLBAR_H = 46;
@@ -6111,13 +6174,36 @@ ipcMain.handle('wa:groups:set', (_e, cfg) => {
6111
6174
  ipcMain.handle('settings:get', () => {
6112
6175
  const out = JSON.parse(JSON.stringify(settings));
6113
6176
  /* Sırların renderera düz metin gitmesini engelle */
6114
- if (out.email && out.email.pass) out.email.pass = '***';
6115
- if (out.waTts && out.waTts.key) out.waTts.key = '***';
6177
+ if (out.email && out.email.pass) out.email.pass = SECRET_MASK;
6178
+ if (out.waTts && out.waTts.key) out.waTts.key = SECRET_MASK;
6179
+ if (out.supermemory && out.supermemory.apiKey) out.supermemory.apiKey = SECRET_MASK;
6180
+ if (out.fallout && Array.isArray(out.fallout.slots)) {
6181
+ for (const s of out.fallout.slots) if (s && s.key) s.key = SECRET_MASK;
6182
+ }
6183
+ if (Array.isArray(out.customProviders)) {
6184
+ for (const p of out.customProviders) if (p && p.key) p.key = SECRET_MASK;
6185
+ }
6116
6186
  return out;
6117
6187
  });
6118
6188
 
6119
6189
  /* ---------------- MCP IPC ---------------- */
6120
6190
 
6191
+ /* UI'a giden raw JSON'da env sırlarını maskeler (düz metin anahtar sızmasın) */
6192
+ function maskMcpRaw(raw) {
6193
+ try {
6194
+ const obj = JSON.parse(String(raw || ''));
6195
+ if (!obj || typeof obj !== 'object' || !obj.servers || typeof obj.servers !== 'object') return raw;
6196
+ for (const s of Object.values(obj.servers)) {
6197
+ if (s && typeof s === 'object' && s.env && typeof s.env === 'object') {
6198
+ for (const k of Object.keys(s.env)) if (String(s.env[k] || '')) s.env[k] = SECRET_MASK;
6199
+ }
6200
+ }
6201
+ return JSON.stringify(obj, null, 2);
6202
+ } catch {
6203
+ return raw;
6204
+ }
6205
+ }
6206
+
6121
6207
  ipcMain.handle('mcp:status', () => {
6122
6208
  try { return mcpMod.status(); } catch { return { path: '', servers: [] }; }
6123
6209
  });
@@ -6127,15 +6213,17 @@ ipcMain.handle('mcp:config:get', () => {
6127
6213
  const p = mcpMod.configPath();
6128
6214
  let raw = '';
6129
6215
  try { raw = require('fs').readFileSync(p, 'utf8'); } catch {}
6130
- return { path: p, raw };
6216
+ return { path: p, raw: maskMcpRaw(raw) };
6131
6217
  } catch { return { path: '', raw: '' }; }
6132
6218
  });
6133
6219
 
6134
6220
  ipcMain.handle('mcp:config:set', (_e, raw) => {
6135
6221
  try {
6136
6222
  const text = String(raw || '');
6137
- if (text.trim()) JSON.parse(text); /* bozuk JSON diske yazılmaz */
6138
- mcpMod.saveConfig(text.trim() ? JSON.parse(text) : { servers: {} });
6223
+ const cfg = text.trim() ? JSON.parse(text) : { servers: {} };
6224
+ /* maskeli env sırlarını mevcut diskteki değerlerle geri koy */
6225
+ try { mcpMod.restoreMaskedSecrets(cfg); } catch {}
6226
+ mcpMod.saveConfig(cfg);
6139
6227
  return { ok: true };
6140
6228
  } catch (e) {
6141
6229
  return { ok: false, error: String((e && e.message) || e) };
@@ -6163,31 +6251,45 @@ ipcMain.handle('fallout:get', () => {
6163
6251
  out.enabled = !!f.enabled;
6164
6252
  out.autoResume = f.autoResume !== false;
6165
6253
  if (Array.isArray(f.slots)) {
6166
- for (let i = 0; i < Math.min(10, f.slots.length); i++) out.slots[i] = f.slots[i] || null;
6254
+ for (let i = 0; i < Math.min(10, f.slots.length); i++) {
6255
+ const s = f.slots[i];
6256
+ /* kayıtlı anahtarlar UI'a maskeli gider */
6257
+ out.slots[i] = s && s.key ? { ...s, key: SECRET_MASK } : s || null;
6258
+ }
6167
6259
  }
6168
6260
  return out;
6169
6261
  });
6170
6262
 
6171
6263
  ipcMain.handle('fallout:set', (_e, cfg) => {
6172
6264
  const cur = settings.fallout || defaultFallout();
6265
+ const prevSlots = Array.isArray(cur.slots) ? cur.slots : [];
6173
6266
  const next = defaultFallout();
6174
6267
  next.enabled = !!(cfg && cfg.enabled);
6175
6268
  next.autoResume = !cfg || cfg.autoResume !== false;
6176
6269
  const slots = Array.isArray(cfg && cfg.slots) ? cfg.slots : [];
6177
6270
  for (let i = 0; i < Math.min(10, slots.length); i++) {
6178
6271
  const s = slots[i];
6179
- if (!s || typeof s !== 'object' || !s.providerId || !s.model || !s.key) continue;
6272
+ if (!s || typeof s !== 'object' || !s.providerId || !s.model) continue;
6273
+ let key = String(s.key || '');
6274
+ /* maskeli/boş anahtar: aynı provider'ın önceki kayıtlı anahtarı korunur */
6275
+ if (key === SECRET_MASK || !key) {
6276
+ const prev = prevSlots[i];
6277
+ key = prev && prev.providerId === String(s.providerId) && prev.key ? String(prev.key) : '';
6278
+ }
6279
+ if (!key) continue;
6180
6280
  next.slots[i] = {
6181
6281
  providerId: String(s.providerId),
6182
6282
  providerName: String(s.providerName || s.providerId),
6183
6283
  model: String(s.model),
6184
- key: String(s.key),
6284
+ key,
6185
6285
  };
6186
6286
  }
6187
6287
  settings.fallout = next;
6188
6288
  saveSettings();
6189
6289
  if (engine) engine.setFallout(next);
6190
- return JSON.parse(JSON.stringify(next));
6290
+ const out = JSON.parse(JSON.stringify(next));
6291
+ for (const s of out.slots) if (s && s.key) s.key = SECRET_MASK;
6292
+ return out;
6191
6293
  });
6192
6294
 
6193
6295
  /* #Limit: provider bazlı max input token limiti + bağlam sıkıştırma */
@@ -6217,10 +6319,13 @@ ipcMain.handle('sec:set', (_e, cfg) => {
6217
6319
  ipcMain.handle('supermemory:set', (_e, cfg) => {
6218
6320
  try {
6219
6321
  const base = String((cfg && cfg.baseUrl) || '').trim() || 'http://localhost:6767';
6322
+ const incomingKey = String((cfg && cfg.apiKey) || '').trim();
6323
+ const prevKey = settings.supermemory && settings.supermemory.apiKey ? String(settings.supermemory.apiKey) : '';
6220
6324
  settings.supermemory = {
6221
6325
  enabled: !!(cfg && cfg.enabled),
6222
6326
  baseUrl: base.replace(/\/+$/, ''),
6223
- apiKey: String((cfg && cfg.apiKey) || '').trim(),
6327
+ /* maskeli anahtar geri gelirse mevcut korunur */
6328
+ apiKey: incomingKey === SECRET_MASK ? prevKey : incomingKey,
6224
6329
  containerTag: String((cfg && cfg.containerTag) || 'beast').trim() || 'beast',
6225
6330
  };
6226
6331
  saveSettings();
@@ -6230,7 +6335,7 @@ ipcMain.handle('supermemory:set', (_e, cfg) => {
6230
6335
  engine._smCheckedAt = 0;
6231
6336
  engine._smWarned = false;
6232
6337
  }
6233
- return { ok: true, supermemory: settings.supermemory };
6338
+ return { ok: true, supermemory: { ...settings.supermemory, apiKey: settings.supermemory.apiKey ? SECRET_MASK : '' } };
6234
6339
  } catch (e) {
6235
6340
  return { ok: false, error: String((e && e.message) || e) };
6236
6341
  }
@@ -6240,7 +6345,7 @@ ipcMain.handle('supermemory:get', () => {
6240
6345
  return {
6241
6346
  enabled: sm.enabled !== false,
6242
6347
  baseUrl: sm.baseUrl || 'http://localhost:6767',
6243
- apiKey: sm.apiKey || '',
6348
+ apiKey: sm.apiKey ? SECRET_MASK : '',
6244
6349
  containerTag: sm.containerTag || 'beast',
6245
6350
  up: engine ? !!engine._smUp : null,
6246
6351
  };
@@ -6809,6 +6914,11 @@ function cronFire(job) {
6809
6914
  const sent = engine.send(sid, {
6810
6915
  text: `[cron: ${job.name}]\n${job.prompt}`,
6811
6916
  });
6917
+ toastNotify(
6918
+ `Cron: ${job.name}`,
6919
+ isReminderJob(job) ? 'Hatırlatma zamanı geldi' : String(job.prompt || '').slice(0, 160),
6920
+ 'cron'
6921
+ );
6812
6922
  if (!sent) cronAnswerPendingDrop(sid); // gönderilemedi — bayat bekleme bırakma
6813
6923
  } catch {}
6814
6924
  cronEmit();
@@ -6826,6 +6936,7 @@ function watcherFire(w, value) {
6826
6936
  w.op === 'changed'
6827
6937
  ? 'izlenen değer değişti'
6828
6938
  : `kural sağlandı (son değer ${value}, koşul ${w.op} ${w.value ?? ''})`;
6939
+ toastNotify(`İzleyici: ${w.name}`, target, 'watchers');
6829
6940
  engine.send(sid, {
6830
6941
  text:
6831
6942
  `[IZLEYICI: ${w.name}] ${target}. ` +
@@ -7317,7 +7428,10 @@ ipcMain.handle('providers:keys', () => {
7317
7428
  }
7318
7429
  } catch {}
7319
7430
  }
7320
- return map;
7431
+ /* UI'a anahtarlar DÜZ METİN gitmez — yalnız "kayıtlı var mı" bilgisi taşınır */
7432
+ const out = {};
7433
+ for (const [k, v] of Object.entries(map)) out[k] = String(v || '') ? SECRET_MASK : '';
7434
+ return out;
7321
7435
  });
7322
7436
  /* #22 izleyici paneli IPC */
7323
7437
  ipcMain.handle('watchers:list', () => watchers.list());
@@ -7357,6 +7471,13 @@ ipcMain.handle('cron:runNow', (_e, id) => {
7357
7471
  cronEmit();
7358
7472
  return r;
7359
7473
  });
7474
+ /* Masaüstü (toast) bildirimleri aç/kapa */
7475
+ ipcMain.handle('notify:get', () => ({ toast: settings.notifyToast !== false }));
7476
+ ipcMain.handle('notify:set', (_e, on) => {
7477
+ settings.notifyToast = !!on;
7478
+ saveSettings();
7479
+ return { toast: settings.notifyToast };
7480
+ });
7360
7481
 
7361
7482
  /* ---------------- tarayıcı IPC ---------------- */
7362
7483
  ipcMain.handle('browser:toggle', () => {
@@ -10266,7 +10387,17 @@ ipcMain.handle('ide:previewUrl', async (_e, url) => {
10266
10387
  });
10267
10388
 
10268
10389
  ipcMain.handle('custom:set', (_e, list) => {
10269
- settings.customProviders = Array.isArray(list) ? list : [];
10390
+ const prev = Array.isArray(settings.customProviders) ? settings.customProviders : [];
10391
+ const byId = new Map(prev.filter((p) => p && p.id).map((p) => [p.id, p]));
10392
+ settings.customProviders = (Array.isArray(list) ? list : []).map((p) => {
10393
+ if (!p || typeof p !== 'object') return p;
10394
+ const item = { ...p };
10395
+ const old = byId.get(item.id);
10396
+ const k = String(item.key == null ? '' : item.key).trim();
10397
+ /* UI'dan maskeli/boş anahtar geldiyse aynı id'nin kayıtlı anahtarı korunur */
10398
+ if ((k === SECRET_MASK || !k) && old && old.key) item.key = String(old.key);
10399
+ return item;
10400
+ });
10270
10401
  saveSettings();
10271
10402
  engine.setCustomProviders(settings.customProviders);
10272
10403
  headroom.syncProviders(); /* headroom açıksa proxy setini yeni listeye uydur */
@@ -10823,6 +10954,10 @@ ipcMain.handle('email:set', (_e, cfg) => {
10823
10954
  saveSettings();
10824
10955
  return { ...settings.email, pass: settings.email.pass ? '***' : '' };
10825
10956
  });
10957
+ /* E-posta paneli: gelen kutusu + okuma + gönderim (engine köprüsüyle aynı fonksiyonlar) */
10958
+ ipcMain.handle('email:list', (_e, opts) => emailList(opts || {}));
10959
+ ipcMain.handle('email:read', (_e, uid) => emailRead(uid));
10960
+ ipcMain.handle('email:send', (_e, msg) => emailSend(msg || {}));
10826
10961
 
10827
10962
  /* ---------- ekran görüntüsü ---------- */
10828
10963
 
@@ -10895,6 +11030,12 @@ async function deliverFile(sessionId, filePath, caption) {
10895
11030
  try {
10896
11031
  const abs = path.isAbsolute(filePath) ? filePath : path.join(engine.workspace || process.cwd(), filePath);
10897
11032
  if (!fs.existsSync(abs)) return { ok: false, error: 'dosya bulunamadı: ' + abs };
11033
+ /* GÜVENLİK: sır/anahtar dosyaları dışa gönderilmez (settings.json,
11034
+ wa-auth, .ssh, .env, *.pem...). Ajan bunu kullanıcıya söylesin. */
11035
+ if (fsguard.isSensitiveSendPath(abs)) {
11036
+ log.warn('sec', `send_file engellendi (hassas yol): ${abs}`);
11037
+ return { ok: false, error: 'güvenlik: hassas dosya (sır/anahtar) dışa gönderilmedi: ' + path.basename(abs) };
11038
+ }
10898
11039
  const name = path.basename(abs);
10899
11040
  const ext = path.extname(abs).toLowerCase();
10900
11041
  let sid = String(sessionId || '');
package/src/preload.js CHANGED
@@ -147,6 +147,8 @@ contextBridge.exposeInMainWorld('beast', {
147
147
  cronDelete: (id) => ipcRenderer.invoke('cron:delete', id),
148
148
  cronToggle: (id) => ipcRenderer.invoke('cron:toggle', id),
149
149
  cronRunNow: (id) => ipcRenderer.invoke('cron:runNow', id),
150
+ notifyGet: () => ipcRenderer.invoke('notify:get'),
151
+ notifySet: (on) => ipcRenderer.invoke('notify:set', on),
150
152
 
151
153
  watchersList: () => ipcRenderer.invoke('watchers:list'),
152
154
 
@@ -222,6 +224,9 @@ contextBridge.exposeInMainWorld('beast', {
222
224
  whereWasISet: (cfg) => ipcRenderer.invoke('wherewasi:set', cfg),
223
225
  getEmail: () => ipcRenderer.invoke('email:get'),
224
226
  setEmail: (cfg) => ipcRenderer.invoke('email:set', cfg),
227
+ emailList: (opts) => ipcRenderer.invoke('email:list', opts),
228
+ emailRead: (uid) => ipcRenderer.invoke('email:read', uid),
229
+ emailSend: (msg) => ipcRenderer.invoke('email:send', msg),
225
230
  captureScreen: () => ipcRenderer.invoke('screen:capture'),
226
231
  waListSessions: () => ipcRenderer.invoke('wa:sessions'),
227
232
  botsList: () => ipcRenderer.invoke('bots:list'),
@@ -166,6 +166,7 @@
166
166
  em_mem_clear: 'Hafızayı Temizle',
167
167
  em_mem_cleared: 'Empati hafızası temizlendi',
168
168
  tab_cron: 'Cron',
169
+ tab_agenda: 'Ajanda',
169
170
  tab_usage: 'Maliyet',
170
171
  tab_logs: 'Log',
171
172
  tab_dash: 'Dashboard',
@@ -656,6 +657,81 @@
656
657
  store_install_fail: 'Kurulum başarısız',
657
658
  store_like_ok: 'Beğeni kaydedildi',
658
659
  store_login_note: 'Beast Kimliği anonimdir; gerçek Beast Kodun asla paylaşılmaz.',
660
+ /* Cron paneli + WhatsApp kişi/yetki editörü */
661
+ cr_empty: 'Henüz görev yok — yukarıdan ekle.',
662
+ cr_dot_on: 'aktif — tıkla: forma doldur',
663
+ cr_dot_off: 'kapalı — tıkla: forma doldur',
664
+ cr_next: 'sıradaki: ',
665
+ cr_last: ' · son: ',
666
+ cr_run: 'Şimdi çalıştır',
667
+ cr_pause: 'Durdur',
668
+ cr_start: 'Başlat',
669
+ cr_del: 'Sil',
670
+ cr_modal_empty: 'Kayıtlı cron görev yok — Ayarlar → Cron sekmesinden ekle.',
671
+ cr_ran: 'Çalıştırıldı',
672
+ cr_toggle_pause: 'Duraklat',
673
+ wa_vazgec: 'Vazgeç',
674
+ wa_kaydet: 'kaydet',
675
+ wa_updated: 'Güncellendi',
676
+ wa_isim_ph: 'isim',
677
+ wa_numara_ph: 'numara',
678
+ wa_no_name_title: 'Bu kişiye cevap VERİLMEYECEK — tıklayıp isim ver',
679
+ wa_edit_title: 'Tıkla: isim/numara düzenle',
680
+ wa_perm_title: 'Yetki seviyesi — bu kişinin agent erişimini belirler',
681
+ wa_owner_title_on: 'Bu kişi BEAST\u2019İN SAHİBİ — tıklayıp başka kişiye devret',
682
+ wa_owner_title_off: 'Tıkla: bu kişiyi SAHİP yap (yalnız biri olabilir)',
683
+ wa_owner_on: '👑 sahip',
684
+ wa_owner_off: 'sahip yap',
685
+ wa_perm_all: 'serbest',
686
+ wa_perm_read: 'okuma',
687
+ wa_perm_chat: 'kısıtlı',
688
+ wa_queue_info: 'Mesaj kuyruğu: ${n} bekleyen',
689
+ wa_queue_failed: '${n} gönderilemedi',
690
+ wa_queue_empty_ok: 'Kuyruk temiz',
691
+ /* Cron düzenleme */
692
+ cr_update: 'Güncelle',
693
+ cr_cancel_edit: 'Vazgeç',
694
+ cr_edit_hint: 'tıkla: düzenle',
695
+ cr_edit_loaded: 'Görev forma yüklendi — düzenleyip Güncelle',
696
+ cr_updated: 'Görev güncellendi',
697
+ cr_added: 'Görev eklendi',
698
+ cr_edit_cancelled: 'Düzenleme iptal edildi',
699
+ /* İzleyici ekleme formu */
700
+ w_add: '+',
701
+ w_added: 'İzleyici eklendi',
702
+ w_add_name: 'İsim',
703
+ w_add_url: 'https://...',
704
+ w_add_path: 'json path (örn: data.price)',
705
+ w_add_value: 'eşik değer',
706
+ w_add_re: 'regex (opsiyonel)',
707
+ w_kind_web: 'Web',
708
+ w_kind_battery: 'Pil',
709
+ w_kind_logs: 'Log',
710
+ w_op_changed: 'değişti',
711
+ /* Ajanda */
712
+ aj_h2: 'Ajanda',
713
+ aj_sub: 'Yaklaşan cron görevleri, hatırlatıcılar ve izleyiciler — en yakın üstte.',
714
+ aj_empty: 'Planlanmış bir şey yok.',
715
+ aj_now: 'şimdi',
716
+ aj_later: 'sonra',
717
+ aj_ago: 'önce',
718
+ aj_notify: 'Masaüstü bildirimleri (toast) — cron, hatırlatıcı, izleyici ve ajan bitişinde',
719
+ aj_notify_on: 'Bildirimler açık',
720
+ aj_notify_off: 'Bildirimler kapalı',
721
+ /* E-posta paneli */
722
+ mail_inbox: 'Gelen Kutusu',
723
+ mail_refresh: 'Yenile',
724
+ mail_compose: 'Yeni Mail',
725
+ mail_to: 'alıcı@ornek.com',
726
+ mail_subject: 'Konu',
727
+ mail_body: 'Mesaj…',
728
+ mail_send: 'Gönder',
729
+ mail_sent: 'Mail gönderildi',
730
+ mail_send_fail: 'Mail gönderilemedi',
731
+ mail_empty: 'Gelen kutusu boş.',
732
+ mail_reply: 'Yanıtla',
733
+ mail_read_fail: 'Mail okunamadı',
734
+ mail_need_to: 'Alıcı adresi gerekli',
659
735
  },
660
736
  en: {
661
737
  newChat: '+ New Chat',
@@ -811,6 +887,7 @@
811
887
  em_mem_clear: 'Clear Memory',
812
888
  em_mem_cleared: 'Empathy memory cleared',
813
889
  tab_cron: 'Cron',
890
+ tab_agenda: 'Agenda',
814
891
  tab_usage: 'Cost',
815
892
  tab_logs: 'Logs',
816
893
  tab_dash: 'Dashboard',
@@ -1076,36 +1153,50 @@
1076
1153
  wa_queue_info: 'Message queue: ${n} pending',
1077
1154
  wa_queue_failed: '${n} could not be sent',
1078
1155
  wa_queue_empty_ok: 'Queue clear',
1079
- cr_empty: 'Henüz görev yok — yukarıdan ekle.',
1080
- cr_dot_on: 'aktif — tıkla: forma doldur',
1081
- cr_dot_off: 'kapalı — tıkla: forma doldur',
1082
- cr_next: 'sıradaki: ',
1083
- cr_last: ' · son: ',
1084
- cr_run: 'Şimdi çalıştır',
1085
- cr_pause: 'Durdur',
1086
- cr_start: 'Başlat',
1087
- cr_del: 'Sil',
1088
- cr_modal_empty: 'Kayıtlı cron görev yok — Ayarlar → Cron sekmesinden ekle.',
1089
- cr_ran: 'Çalıştırıldı',
1090
- cr_toggle_pause: 'Duraklat',
1091
- wa_vazgec: 'Vazgeç',
1092
- wa_kaydet: 'kaydet',
1093
- wa_updated: 'Güncellendi',
1094
- wa_isim_ph: 'isim',
1095
- wa_numara_ph: 'numara',
1096
- wa_no_name_title: 'Bu kişiye cevap VERİLMEYECEK — tıklayıp isim ver',
1097
- wa_edit_title: 'Tıkla: isim/numara düzenle',
1098
- wa_perm_title: 'Yetki seviyesi — bu kişinin agent erişimini belirler',
1099
- wa_owner_title_on: 'Bu kişi BEAST\u2019İN SAHİBİ — tıklayıp başka kişiye devret',
1100
- wa_owner_title_off: 'Tıkla: bu kişiyi SAHİP yap (yalnız biri olabilir)',
1101
- wa_owner_on: '👑 sahip',
1102
- wa_owner_off: 'sahip yap',
1103
- wa_perm_all: 'serbest',
1104
- wa_perm_read: 'okuma',
1105
- wa_perm_chat: 'kısıtlı',
1106
- wa_queue_info: 'Mesaj kuyruğu: ${n} bekleyen',
1107
- wa_queue_failed: '${n} gönderilemedi',
1108
- wa_queue_empty_ok: 'Kuyruk temiz',
1156
+ /* Cron edit */
1157
+ cr_update: 'Update',
1158
+ cr_cancel_edit: 'Cancel',
1159
+ cr_edit_hint: 'click: edit',
1160
+ cr_edit_loaded: 'Task loaded into form — edit and Update',
1161
+ cr_updated: 'Task updated',
1162
+ cr_added: 'Task added',
1163
+ cr_edit_cancelled: 'Edit cancelled',
1164
+ /* Watcher add form */
1165
+ w_add: '+',
1166
+ w_added: 'Watcher added',
1167
+ w_add_name: 'Name',
1168
+ w_add_url: 'https://...',
1169
+ w_add_path: 'json path (e.g. data.price)',
1170
+ w_add_value: 'threshold value',
1171
+ w_add_re: 'regex (optional)',
1172
+ w_kind_web: 'Web',
1173
+ w_kind_battery: 'Battery',
1174
+ w_kind_logs: 'Log',
1175
+ w_op_changed: 'changed',
1176
+ /* Agenda */
1177
+ aj_h2: 'Agenda',
1178
+ aj_sub: 'Upcoming cron jobs, reminders and watchers — nearest first.',
1179
+ aj_empty: 'Nothing scheduled.',
1180
+ aj_now: 'now',
1181
+ aj_later: 'from now',
1182
+ aj_ago: 'ago',
1183
+ aj_notify: 'Desktop notifications (toast) — on cron, reminder, watcher and agent finish',
1184
+ aj_notify_on: 'Notifications on',
1185
+ aj_notify_off: 'Notifications off',
1186
+ /* Email panel */
1187
+ mail_inbox: 'Inbox',
1188
+ mail_refresh: 'Refresh',
1189
+ mail_compose: 'New Mail',
1190
+ mail_to: 'to@example.com',
1191
+ mail_subject: 'Subject',
1192
+ mail_body: 'Message…',
1193
+ mail_send: 'Send',
1194
+ mail_sent: 'Mail sent',
1195
+ mail_send_fail: 'Could not send mail',
1196
+ mail_empty: 'Inbox is empty.',
1197
+ mail_reply: 'Reply',
1198
+ mail_read_fail: 'Could not read mail',
1199
+ mail_need_to: 'Recipient address required',
1109
1200
  us_reset_toast: 'Usage counters reset',
1110
1201
  us_backup_ok: 'Backup taken: Desktop\\Beast-Backups',
1111
1202
  us_where_on_toast: 'Startup summary on',
@@ -455,6 +455,7 @@
455
455
  <button class="tab" data-tab="events" data-i18n="tab_events">Olay Merkezi</button>
456
456
  <button class="tab" data-tab="empati" data-i18n="tab_empati">Empati Loop</button>
457
457
  <button class="tab" data-tab="cron" data-i18n="tab_cron">Cron</button>
458
+ <button class="tab" data-tab="agenda" data-i18n="tab_agenda">Ajanda</button>
458
459
  <button class="tab" data-tab="usage" data-i18n="tab_usage">Maliyet · Limit</button>
459
460
  <button class="tab" data-tab="headroom" data-i18n="tab_headroom">Token Sıkıştırma</button>
460
461
  <button class="tab" data-tab="logs" data-i18n="tab_logs">Log</button>
@@ -506,9 +507,11 @@
506
507
  </div>
507
508
  <textarea id="cronPrompt" rows="3" placeholder="Agent'a verilecek görev…" data-i18n-ph="cron_prompt_ph"></textarea>
508
509
  <button id="cronAddBtn" data-i18n="cron_add">+ Görev Ekle</button>
510
+ <button id="cronCancel" class="ghost" hidden data-i18n="cr_cancel_edit">Vazgeç</button>
509
511
  </div>
510
512
  <div id="cronList"></div>
511
513
  </div>
514
+ <div id="tab-agenda" class="pane" hidden></div>
512
515
  </section>
513
516
  </div>
514
517
  </div>
@@ -612,6 +615,43 @@
612
615
  <button class="mini-close" id="watchClose" title="Kapat" data-i18n-title="btn_close">&#x00D7;</button>
613
616
  </div>
614
617
  <div class="mini-body">
618
+ <div id="wAddBox">
619
+ <div class="mini-add-row">
620
+ <input id="wAddName" class="inp" data-i18n-ph="w_add_name" placeholder="İsim" maxlength="80" />
621
+ <select id="wAddKind" class="inp mini-select">
622
+ <option value="web" data-i18n="w_kind_web">Web</option>
623
+ <option value="battery" data-i18n="w_kind_battery">Pil</option>
624
+ <option value="logs" data-i18n="w_kind_logs">Log</option>
625
+ </select>
626
+ <input id="wAddEvery" class="inp" type="number" min="1" max="1440" value="15" title="dakika" style="width:70px" />
627
+ <button id="wAddBtn" data-i18n="w_add">+</button>
628
+ </div>
629
+ <div class="mini-add-row" id="wAddWeb">
630
+ <input id="wAddUrl" class="inp" data-i18n-ph="w_add_url" placeholder="https://..." />
631
+ <input id="wAddPath" class="inp" data-i18n-ph="w_add_path" placeholder="json path (örn: data.price)" />
632
+ </div>
633
+ <div class="mini-add-row" id="wAddCond">
634
+ <select id="wAddOp" class="inp mini-select">
635
+ <option value="lte">&lt;=</option>
636
+ <option value="lt">&lt;</option>
637
+ <option value="gte">&gt;=</option>
638
+ <option value="gt">&gt;</option>
639
+ <option value="eq">=</option>
640
+ <option value="neq">≠</option>
641
+ <option value="changed" data-i18n="w_op_changed">değişti</option>
642
+ </select>
643
+ <input id="wAddValue" class="inp" data-i18n-ph="w_add_value" placeholder="eşik değer" />
644
+ </div>
645
+ <div class="mini-add-row" id="wAddLogs" hidden>
646
+ <select id="wAddLevel" class="inp mini-select">
647
+ <option value="error">error</option>
648
+ <option value="warn">warn</option>
649
+ <option value="info">info</option>
650
+ </select>
651
+ <input id="wAddWindow" class="inp" type="number" min="1" max="720" value="10" title="pencere (dk)" style="width:80px" />
652
+ <input id="wAddRe" class="inp" data-i18n-ph="w_add_re" placeholder="regex (opsiyonel)" />
653
+ </div>
654
+ </div>
615
655
  <div id="watchList" class="mini-list"></div>
616
656
  </div>
617
657
  </div>