beast-agent 2.31.0 → 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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "beast-agent",
3
3
  "productName": "Beast Agent",
4
- "version": "2.31.0",
4
+ "version": "2.32.0",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
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');
@@ -272,6 +272,8 @@ try {
272
272
  try { nodemailer = require('nodemailer'); } catch {}
273
273
 
274
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 {}
275
277
  /* mem0-native embedding modeli whisper ile AYNI cache'i kullanır (src/agent/mem0.js okur) */
276
278
  process.env.BEAST_MODELS_DIR = path.join(APP_DIR, 'models');
277
279
 
@@ -301,6 +303,8 @@ for (const d of [APP_DIR, SESSIONS_DIR]) fs.mkdirSync(d, { recursive: true });
301
303
 
302
304
  let win = null;
303
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
304
308
  let settings = loadSettings();
305
309
  ensureBeastCode();
306
310
  startHealthServer(); /* splash/boot aşamasından itibaren /health ayakta */
@@ -3232,6 +3236,17 @@ function reloadBackend() {
3232
3236
  },
3233
3237
  emit: (ev) => {
3234
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
+ }
3235
3250
  /* opencode permission (BC): ask kanalı UI'a zaten yukarıda düştü — WA
3236
3251
  sahibine de tek satır bildir gitsin (/approve · /approve always · /deny) */
3237
3252
  if (ev.type === 'permission.asked' && ev.request && wa && wa.connected) {
@@ -3741,6 +3756,30 @@ function showWin() {
3741
3756
  }
3742
3757
  }
3743
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
+
3744
3783
  /* ---------------- dahili tarayıcı ---------------- */
3745
3784
 
3746
3785
  const BROWSER_TOOLBAR_H = 46;
@@ -6875,6 +6914,11 @@ function cronFire(job) {
6875
6914
  const sent = engine.send(sid, {
6876
6915
  text: `[cron: ${job.name}]\n${job.prompt}`,
6877
6916
  });
6917
+ toastNotify(
6918
+ `Cron: ${job.name}`,
6919
+ isReminderJob(job) ? 'Hatırlatma zamanı geldi' : String(job.prompt || '').slice(0, 160),
6920
+ 'cron'
6921
+ );
6878
6922
  if (!sent) cronAnswerPendingDrop(sid); // gönderilemedi — bayat bekleme bırakma
6879
6923
  } catch {}
6880
6924
  cronEmit();
@@ -6892,6 +6936,7 @@ function watcherFire(w, value) {
6892
6936
  w.op === 'changed'
6893
6937
  ? 'izlenen değer değişti'
6894
6938
  : `kural sağlandı (son değer ${value}, koşul ${w.op} ${w.value ?? ''})`;
6939
+ toastNotify(`İzleyici: ${w.name}`, target, 'watchers');
6895
6940
  engine.send(sid, {
6896
6941
  text:
6897
6942
  `[IZLEYICI: ${w.name}] ${target}. ` +
@@ -7426,6 +7471,13 @@ ipcMain.handle('cron:runNow', (_e, id) => {
7426
7471
  cronEmit();
7427
7472
  return r;
7428
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
+ });
7429
7481
 
7430
7482
  /* ---------------- tarayıcı IPC ---------------- */
7431
7483
  ipcMain.handle('browser:toggle', () => {
@@ -10902,6 +10954,10 @@ ipcMain.handle('email:set', (_e, cfg) => {
10902
10954
  saveSettings();
10903
10955
  return { ...settings.email, pass: settings.email.pass ? '***' : '' };
10904
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 || {}));
10905
10961
 
10906
10962
  /* ---------- ekran görüntüsü ---------- */
10907
10963
 
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',
@@ -687,6 +688,50 @@
687
688
  wa_queue_info: 'Mesaj kuyruğu: ${n} bekleyen',
688
689
  wa_queue_failed: '${n} gönderilemedi',
689
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',
690
735
  },
691
736
  en: {
692
737
  newChat: '+ New Chat',
@@ -842,6 +887,7 @@
842
887
  em_mem_clear: 'Clear Memory',
843
888
  em_mem_cleared: 'Empathy memory cleared',
844
889
  tab_cron: 'Cron',
890
+ tab_agenda: 'Agenda',
845
891
  tab_usage: 'Cost',
846
892
  tab_logs: 'Logs',
847
893
  tab_dash: 'Dashboard',
@@ -1107,6 +1153,50 @@
1107
1153
  wa_queue_info: 'Message queue: ${n} pending',
1108
1154
  wa_queue_failed: '${n} could not be sent',
1109
1155
  wa_queue_empty_ok: 'Queue clear',
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',
1110
1200
  us_reset_toast: 'Usage counters reset',
1111
1201
  us_backup_ok: 'Backup taken: Desktop\\Beast-Backups',
1112
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>
@@ -229,6 +229,7 @@ const els = {
229
229
  cronSchedule: $('#cronSchedule'),
230
230
  cronPrompt: $('#cronPrompt'),
231
231
  cronAddBtn: $('#cronAddBtn'),
232
+ cronCancel: $('#cronCancel'),
232
233
  cronList: $('#cronList'),
233
234
  gearBtn: $('#gearBtn'),
234
235
  settingsOverlay: $('#settingsOverlay'),
@@ -1242,6 +1243,7 @@ async function renderActiveSettingsTab() {
1242
1243
  case 'events': await renderEventsPane(); break;
1243
1244
  case 'empati': await renderEmpatiPane(); break;
1244
1245
  case 'cron': await openCron(); break;
1246
+ case 'agenda': await renderAgendaPane(); break;
1245
1247
  case 'usage': await renderUsagePane(); break;
1246
1248
  case 'headroom': await renderHeadroomPane(); break;
1247
1249
  case 'agents': await refreshAgentsPane(); break;
@@ -1327,12 +1329,14 @@ function switchTab(name) {
1327
1329
  document.querySelectorAll('#setTabs .tab').forEach((b) =>
1328
1330
  b.classList.toggle('active', b.dataset.tab === name)
1329
1331
  );
1330
- for (const p of ['lang', 'provider', 'fallout', 'skills', 'agents', 'tts', 'install', 'email', 'integrations', 'websearch', 'mcp', 'events', 'empati', 'cron', 'usage', 'headroom', 'logs', 'dash', 'sec', 'update']) {
1332
+ for (const p of ['lang', 'provider', 'fallout', 'skills', 'agents', 'tts', 'install', 'email', 'integrations', 'websearch', 'mcp', 'events', 'empati', 'cron', 'agenda', 'usage', 'headroom', 'logs', 'dash', 'sec', 'update']) {
1331
1333
  const el = $('#tab-' + p);
1332
1334
  if (el) el.hidden = p !== name; // guard: eksik pane tüm sekmeleri kilitlemesin
1333
1335
  }
1334
1336
  if (name === 'lang') renderLangPane();
1335
1337
  if (name === 'cron') openCron();
1338
+ if (name === 'agenda') renderAgendaPane();
1339
+ if (name === 'email') renderEmailPane();
1336
1340
  if (name === 'usage') renderUsagePane();
1337
1341
  if (name === 'install') renderInstallPane();
1338
1342
  if (name === 'headroom') renderHeadroomPane();
@@ -2640,7 +2644,25 @@ async function renderEmailPane() {
2640
2644
  <input id="mailSmtpPort" class="inp" placeholder="465" autocomplete="off" />
2641
2645
  <button id="mailSave" class="btn ghost">${_t('mail_save')}</button>
2642
2646
  </div>` +
2643
- '<div class="sub" style="margin-top:8px">' + _t('mail_note') + '</div>';
2647
+ '<div class="sub" style="margin-top:8px">' + _t('mail_note') + '</div>' +
2648
+ '<div class="divider"></div>' +
2649
+ '<h2>' + _t('mail_inbox') + '</h2>' +
2650
+ '<div style="display:flex;gap:8px;margin:6px 0;align-items:center">' +
2651
+ '<button id="mailRefresh" class="btn ghost">' + _t('mail_refresh') + '</button>' +
2652
+ '<button id="mailCompose" class="btn ghost">' + _t('mail_compose') + '</button>' +
2653
+ '<span id="mailMsg" class="sub" style="margin:0"></span></div>' +
2654
+ '<div id="mailList"></div>' +
2655
+ '<div id="mailRead" hidden style="margin-top:10px"></div>' +
2656
+ '<div id="mailWrite" hidden style="margin-top:10px">' +
2657
+ '<div class="form-grid" style="grid-template-columns:1fr 1fr">' +
2658
+ '<input id="mwTo" class="inp" placeholder="' + _t('mail_to') + '" autocomplete="off" />' +
2659
+ '<input id="mwSubject" class="inp" placeholder="' + _t('mail_subject') + '" autocomplete="off" />' +
2660
+ '</div>' +
2661
+ '<textarea id="mwBody" class="inp" rows="6" placeholder="' + _t('mail_body') + '" style="margin-top:6px;width:100%"></textarea>' +
2662
+ '<div style="display:flex;gap:8px;margin-top:6px">' +
2663
+ '<button id="mwSend" class="btn">' + _t('mail_send') + '</button>' +
2664
+ '<button id="mwCancel" class="btn ghost">' + _t('btn_close') + '</button>' +
2665
+ '</div></div>';
2644
2666
 
2645
2667
  try {
2646
2668
  const em = await beast.getEmail();
@@ -2672,6 +2694,86 @@ async function renderEmailPane() {
2672
2694
  if (pass && pass !== PASS_MASK) { savedPass = pass; mailPass.value = PASS_MASK; passChanged = false; }
2673
2695
  toast(_t('mail_saved'));
2674
2696
  });
2697
+
2698
+ /* ---- gelen kutusu + okuma + gönderme ---- */
2699
+ const mailMsg = $('#mailMsg');
2700
+ const mailList = $('#mailList');
2701
+ const mailRead = $('#mailRead');
2702
+ const mailWrite = $('#mailWrite');
2703
+ const openWrite = (to, subject) => {
2704
+ if (!mailWrite) return;
2705
+ mailWrite.hidden = false;
2706
+ $('#mwTo').value = to || '';
2707
+ $('#mwSubject').value = subject || '';
2708
+ $('#mwBody').value = '';
2709
+ $('#mwBody').focus();
2710
+ };
2711
+ const loadInbox = async () => {
2712
+ if (!mailList) return;
2713
+ if (mailMsg) mailMsg.textContent = '…';
2714
+ if (mailRead) mailRead.hidden = true;
2715
+ const r = await beast.emailList({ limit: 15 }).catch((e) => ({ ok: false, error: String((e && e.message) || e) }));
2716
+ if (mailMsg) mailMsg.textContent = '';
2717
+ if (!r || r.ok === false) {
2718
+ mailList.innerHTML = '<div class="sub">' + escapeHtml((r && r.error) || '?') + '</div>';
2719
+ return;
2720
+ }
2721
+ const msgs = r.messages || [];
2722
+ mailList.innerHTML = msgs.length
2723
+ ? msgs
2724
+ .map(
2725
+ (m) =>
2726
+ '<div class="cj-job" data-uid="' + escapeHtml(String(m.uid)) + '" style="cursor:pointer">' +
2727
+ '<div class="cj-info"><div class="cj-name">' + escapeHtml(m.subject || '(konu yok)') + '</div>' +
2728
+ '<div class="cj-meta">' + escapeHtml(m.from || '') + ' · ' + escapeHtml(fmtWhen(m.date)) + '</div></div></div>'
2729
+ )
2730
+ .join('')
2731
+ : '<div class="sub">' + _t('mail_empty') + '</div>';
2732
+ mailList.querySelectorAll('[data-uid]').forEach((row) =>
2733
+ row.addEventListener('click', async () => {
2734
+ const uid = Number(row.dataset.uid);
2735
+ const subjectEl = row.querySelector('.cj-name');
2736
+ if (mailRead) {
2737
+ mailRead.hidden = false;
2738
+ mailRead.innerHTML = '<div class="sub">…</div>';
2739
+ }
2740
+ const rr = await beast.emailRead(uid).catch(() => null);
2741
+ if (!mailRead) return;
2742
+ if (rr && rr.ok) {
2743
+ mailRead.innerHTML =
2744
+ '<div class="sub" style="white-space:pre-wrap;max-height:220px;overflow:auto">' +
2745
+ escapeHtml(rr.content || '') +
2746
+ '</div><button id="mailReply" class="btn ghost" style="margin-top:6px">' + _t('mail_reply') + '</button>';
2747
+ const rp = $('#mailReply');
2748
+ if (rp)
2749
+ rp.addEventListener('click', () =>
2750
+ openWrite('', 'Re: ' + (subjectEl ? subjectEl.textContent : ''))
2751
+ );
2752
+ } else {
2753
+ mailRead.innerHTML = '<div class="sub">' + escapeHtml((rr && rr.error) || _t('mail_read_fail')) + '</div>';
2754
+ }
2755
+ })
2756
+ );
2757
+ };
2758
+ if ($('#mailRefresh')) $('#mailRefresh').addEventListener('click', loadInbox);
2759
+ if ($('#mailCompose')) $('#mailCompose').addEventListener('click', () => openWrite('', ''));
2760
+ if ($('#mwCancel')) $('#mwCancel').addEventListener('click', () => { if (mailWrite) mailWrite.hidden = true; });
2761
+ if ($('#mwSend')) $('#mwSend').addEventListener('click', async () => {
2762
+ const to = $('#mwTo').value.trim();
2763
+ const subject = $('#mwSubject').value.trim();
2764
+ const body = $('#mwBody').value;
2765
+ if (!to) { toast(_t('mail_need_to')); return; }
2766
+ const r = await beast.emailSend({ to, subject, body }).catch((e) => ({ ok: false, error: String((e && e.message) || e) }));
2767
+ if (r && r.ok) {
2768
+ toast(_t('mail_sent'));
2769
+ if (mailWrite) mailWrite.hidden = true;
2770
+ loadInbox();
2771
+ } else {
2772
+ toast((r && r.error) || _t('mail_send_fail'));
2773
+ }
2774
+ });
2775
+ /* ayar kayıtlıysa ve sekme GÖRÜNÜRse gelen kutusunu yükle (boşuna IMAP bağlantısı yok) */
2776
+ if (savedPass && $('#mailUser').value.trim() && !pane.hidden) loadInbox();
2675
2777
  }
2676
2778
 
2677
2779
  /* ---------------- integrations (WhatsApp) ---------------- */
@@ -5572,7 +5674,15 @@ function renderAgentsPane() {
5572
5674
  /* ---------------- events from engine ---------------- */
5573
5675
 
5574
5676
  function onEvent(ev) {
5575
- if (ev.type === 'sessions') { refreshSessions(); return; }
5677
+ if (ev.type === 'notify-click') {
5678
+ /* toast tıklandı: ilgili panele atla */
5679
+ if (ev.target === 'watchers') openWatchModal();
5680
+ else if (ev.target === 'cron') openSettings().then(() => switchTab('cron')).catch(() => {});
5681
+ else if (ev.target === 'agents') { toggleRail(true); refreshAgentsPane(); }
5682
+ else openSettings().catch(() => {});
5683
+ return;
5684
+ }
5685
+ if (ev.type === 'sessions') { refreshSessions(); return; }
5576
5686
  if (ev.type === 'approval') {
5577
5687
  /* Beast Studio oturumunun onayı: panelde ipucu + kart sohbete düşer */
5578
5688
  if (stSessionId && ev.sessionId === stSessionId) {
@@ -6333,6 +6443,13 @@ async function openWatchLogModal(id) {
6333
6443
  }
6334
6444
 
6335
6445
  let lastCronKey = '';
6446
+ /* Cron DÜZENLEME: satıra tıkla → form dolar, buton "Güncelle"ye döner */
6447
+ let editingCronId = '';
6448
+ function resetCronEdit() {
6449
+ editingCronId = '';
6450
+ if (els.cronAddBtn) els.cronAddBtn.textContent = _t('cron_add');
6451
+ if (els.cronCancel) els.cronCancel.hidden = true;
6452
+ }
6336
6453
  async function renderCronModal(force) {
6337
6454
  let jobs = [];
6338
6455
  try { jobs = (await beast.cronList()) || []; } catch {}
@@ -6755,18 +6872,116 @@ function renderCronList(jobs) {
6755
6872
  btns.appendChild(mkBtn(j.enabled ? '❚❚' : '▶', j.enabled ? _t('cr_pause') : _t('cr_start'), async () => { await beast.cronToggle(j.id); refreshCron(); }));
6756
6873
  btns.appendChild(mkBtn('×', _t('cr_del'), async () => { await beast.cronDelete(j.id); refreshCron(); }));
6757
6874
  row.appendChild(btns);
6758
- dot.addEventListener('click', () => {
6759
- els.cronName.value = j.name;
6875
+ const loadEdit = () => {
6876
+ editingCronId = j.id;
6877
+ els.cronName.value = j.name || '';
6760
6878
  els.cronPreset.value = '__custom';
6761
- els.cronSchedule.value = j.schedule;
6762
- els.cronPrompt.value = j.prompt;
6763
- });
6879
+ els.cronSchedule.value = j.schedule || '';
6880
+ els.cronPrompt.value = j.prompt || '';
6881
+ if (els.cronAddBtn) els.cronAddBtn.textContent = _t('cr_update');
6882
+ if (els.cronCancel) els.cronCancel.hidden = false;
6883
+ els.cronName.focus();
6884
+ toast(_t('cr_edit_loaded'));
6885
+ };
6886
+ dot.addEventListener('click', loadEdit);
6887
+ info.addEventListener('click', loadEdit);
6888
+ dot.title = (j.enabled ? _t('cr_dot_on') : _t('cr_dot_off')) + ' — ' + _t('cr_edit_hint');
6764
6889
  els.cronList.appendChild(row);
6765
6890
  }
6766
6891
  }
6767
6892
 
6768
6893
  function openCron() { refreshCron(); }
6769
6894
 
6895
+ /* ---------------- AJANDA: yaklaşan cron + hatırlatıcı + izleyiciler ---------------- */
6896
+
6897
+ function isReminderLike(j) {
6898
+ if (!j) return false;
6899
+ if (j.kind === 'reminder') return true;
6900
+ const n = String(j.name || '');
6901
+ const p = String(j.prompt || '');
6902
+ return (
6903
+ /^Hatırlatma:/.test(n) ||
6904
+ /^Tekrarlı hatırlatma:/.test(n) ||
6905
+ p.startsWith('[HATIRLATMA ZAMANI]') ||
6906
+ p.startsWith('[TEKRARLI HATIRLATMA]')
6907
+ );
6908
+ }
6909
+
6910
+ function relTime(ms) {
6911
+ const diff = ms - Date.now();
6912
+ const abs = Math.abs(diff);
6913
+ const dk = Math.round(abs / 60000);
6914
+ const saat = Math.floor(dk / 60);
6915
+ const gun = Math.floor(saat / 24);
6916
+ let label;
6917
+ if (dk < 1) return _t('aj_now');
6918
+ else if (dk < 60) label = dk + ' dk';
6919
+ else if (saat < 24) label = saat + ' sa';
6920
+ else label = gun + ' gün';
6921
+ return diff >= 0 ? label + ' ' + _t('aj_later') : label + ' ' + _t('aj_ago');
6922
+ }
6923
+
6924
+ async function renderAgendaPane() {
6925
+ const pane = $('#tab-agenda');
6926
+ if (!pane) return;
6927
+ let jobs = [];
6928
+ let ws = [];
6929
+ try { jobs = (await beast.cronList()) || []; } catch {}
6930
+ try { ws = (await beast.watchersList()) || []; } catch {}
6931
+ const items = [];
6932
+ for (const j of jobs) {
6933
+ if (!j.enabled) continue;
6934
+ const t = j.nextRunAt ? new Date(j.nextRunAt).getTime() : 0;
6935
+ if (!t) continue;
6936
+ items.push({ type: isReminderLike(j) ? 'rem' : 'cron', name: j.name || j.id, at: t, meta: j.schedule, id: j.id });
6937
+ }
6938
+ for (const w of ws) {
6939
+ if (!w.enabled) continue;
6940
+ const every = w.everySec ? w.everySec * 1000 : (w.everyMin || 15) * 60000;
6941
+ const last = w.lastCheckAt ? new Date(w.lastCheckAt).getTime() : 0;
6942
+ items.push({
6943
+ type: 'watch',
6944
+ name: w.name,
6945
+ at: last ? last + every : Date.now(),
6946
+ meta: opText(w) + ' · her ' + fmtEvery(w),
6947
+ id: w.id,
6948
+ });
6949
+ }
6950
+ items.sort((a, b) => a.at - b.at);
6951
+ const badge = { cron: '🕐', rem: '⏰', watch: '👁' };
6952
+ const rows = items.length
6953
+ ? items
6954
+ .map(
6955
+ (it) =>
6956
+ '<div class="cron-job ag-item" data-type="' + it.type + '" data-id="' + escapeHtml(it.id) + '" style="cursor:pointer">' +
6957
+ '<div class="cj-info"><div class="cj-name">' + badge[it.type] + ' ' + escapeHtml(it.name) + '</div>' +
6958
+ '<div class="cj-meta">' + escapeHtml(String(it.meta || '')) + ' · <b>' + escapeHtml(relTime(it.at)) + '</b> · ' + escapeHtml(fmtTime(it.at)) + '</div></div></div>'
6959
+ )
6960
+ .join('')
6961
+ : '<div class="cron-empty">' + _t('aj_empty') + '</div>';
6962
+ let notifOn = true;
6963
+ try { notifOn = !!(await beast.notifyGet()).toast; } catch {}
6964
+ pane.innerHTML =
6965
+ '<h2>' + _t('aj_h2') + '</h2><div class="sub">' + _t('aj_sub') + '</div>' +
6966
+ '<label class="lock-row" style="margin-top:8px"><input type="checkbox" id="ajNotify" ' + (notifOn ? 'checked' : '') + '/><span>' + _t('aj_notify') + '</span></label>' +
6967
+ '<div style="margin-top:10px">' + rows + '</div>';
6968
+ const nj = $('#ajNotify');
6969
+ if (nj) nj.addEventListener('change', async (e) => {
6970
+ await beast.notifySet(e.target.checked).catch(() => {});
6971
+ toast(e.target.checked ? _t('aj_notify_on') : _t('aj_notify_off'));
6972
+ });
6973
+ pane.querySelectorAll('.ag-item').forEach((row) => {
6974
+ row.addEventListener('click', () => {
6975
+ if (row.dataset.type === 'watch') {
6976
+ closeSettings();
6977
+ openWatchModal();
6978
+ } else {
6979
+ switchTab('cron');
6980
+ }
6981
+ });
6982
+ });
6983
+ }
6984
+
6770
6985
  /* ---------------- boot ---------------- */
6771
6986
 
6772
6987
  async function init() {
@@ -7161,6 +7376,59 @@ async function init() {
7161
7376
  if (els.watchLogOverlay) els.watchLogOverlay.addEventListener('click', (e) => {
7162
7377
  if (e.target === els.watchLogOverlay) closeWatchLogModal();
7163
7378
  });
7379
+
7380
+ /* watcher EKLEME formu: tür seçimine göre alanlar açılır/kapanır */
7381
+ const wKindSel = $('#wAddKind');
7382
+ const wSyncKind = () => {
7383
+ const k = wKindSel ? wKindSel.value : 'web';
7384
+ const web = $('#wAddWeb');
7385
+ const cond = $('#wAddCond');
7386
+ const logs = $('#wAddLogs');
7387
+ if (web) web.hidden = k !== 'web';
7388
+ if (cond) cond.hidden = k === 'logs';
7389
+ if (logs) logs.hidden = k !== 'logs';
7390
+ };
7391
+ if (wKindSel) {
7392
+ wKindSel.addEventListener('change', wSyncKind);
7393
+ wSyncKind();
7394
+ }
7395
+ const wAddBtn = $('#wAddBtn');
7396
+ if (wAddBtn) {
7397
+ wAddBtn.addEventListener('click', async () => {
7398
+ const kind = wKindSel ? wKindSel.value : 'web';
7399
+ const input = {
7400
+ name: $('#wAddName').value.trim(),
7401
+ kind,
7402
+ everyMin: Number($('#wAddEvery').value) || 15,
7403
+ };
7404
+ if (kind === 'web') {
7405
+ input.url = $('#wAddUrl').value.trim();
7406
+ input.path = $('#wAddPath').value.trim();
7407
+ }
7408
+ if (kind !== 'logs') {
7409
+ input.op = $('#wAddOp').value;
7410
+ const v = $('#wAddValue').value.trim();
7411
+ input.value = input.op === 'changed' || v === '' ? null : (Number.isNaN(Number(v)) ? v : Number(v));
7412
+ } else {
7413
+ input.level = $('#wAddLevel').value;
7414
+ input.windowMin = Number($('#wAddWindow').value) || 10;
7415
+ input.re = $('#wAddRe').value.trim();
7416
+ input.op = 'gt';
7417
+ input.value = 0;
7418
+ }
7419
+ const r = await beast.watchersAdd(input).catch((e) => ({ ok: false, error: String((e && e.message) || e) }));
7420
+ if (!r || r.ok === false) { toast((r && r.error) || 'İzleyici eklenemedi'); return; }
7421
+ for (const id of ['#wAddName', '#wAddUrl', '#wAddPath', '#wAddValue', '#wAddRe']) {
7422
+ const el = $(id);
7423
+ if (el) el.value = '';
7424
+ }
7425
+ lastWatchKey = '';
7426
+ lastWatchPaneKey = '';
7427
+ await renderWatchersModal(true);
7428
+ await renderWatchersPane(true);
7429
+ toast(_t('w_added'));
7430
+ });
7431
+ }
7164
7432
  if (els.cronOverlay) els.cronOverlay.addEventListener('click', (e) => {
7165
7433
  if (e.target === els.cronOverlay) closeCronModal();
7166
7434
  });
@@ -7173,12 +7441,26 @@ async function init() {
7173
7441
  const schedule = els.cronSchedule.value.trim();
7174
7442
  const prompt = els.cronPrompt.value.trim();
7175
7443
  if (!schedule || !prompt) { toast('Cron ifadesi ve görev metni gerekli'); return; }
7176
- const r = await beast.cronAdd({ name, schedule, prompt });
7177
- if (!r.ok) { toast(r.error || 'Eklenemedi'); return; }
7444
+ if (editingCronId) {
7445
+ const r = await beast.cronUpdate(editingCronId, { name, schedule, prompt });
7446
+ if (!r || r.ok === false) { toast((r && r.error) || 'Güncellenemedi'); return; }
7447
+ toast(_t('cr_updated'));
7448
+ } else {
7449
+ const r = await beast.cronAdd({ name, schedule, prompt });
7450
+ if (!r.ok) { toast(r.error || 'Eklenemedi'); return; }
7451
+ toast(_t('cr_added'));
7452
+ }
7178
7453
  els.cronName.value = ''; els.cronSchedule.value = ''; els.cronPrompt.value = '';
7179
- toast('Görev eklendi');
7454
+ resetCronEdit();
7180
7455
  refreshCron();
7181
7456
  });
7457
+ if (els.cronCancel) {
7458
+ els.cronCancel.addEventListener('click', () => {
7459
+ els.cronName.value = ''; els.cronSchedule.value = ''; els.cronPrompt.value = '';
7460
+ resetCronEdit();
7461
+ toast(_t('cr_edit_cancelled'));
7462
+ });
7463
+ }
7182
7464
  document.addEventListener('mousemove', (e) => {
7183
7465
  if (!rz) return;
7184
7466
  const w = Math.max(300, Math.min(rz.sw - (e.clientX - rz.sx), window.innerWidth - 340));
@@ -1484,6 +1484,19 @@ body.browser-open .mini-overlay { right: var(--bw, 480px); }
1484
1484
  font-size: 12px;
1485
1485
  padding: 4px 6px;
1486
1486
  }
1487
+ /* ---------- İzleyici EKLEME formu ---------- */
1488
+ #wAddBox {
1489
+ display: flex; flex-direction: column; gap: 6px;
1490
+ background: var(--panel); border: 1px solid var(--border);
1491
+ border-radius: 9px; padding: 8px; margin-bottom: 10px;
1492
+ }
1493
+ .mini-add-row { display: flex; gap: 6px; align-items: center; }
1494
+ .mini-add-row .inp { flex: 1; min-width: 0; margin: 0; padding: 5px 8px; font-size: 12px; }
1495
+ #wAddBtn {
1496
+ flex: none; background: var(--accent); color: #fff; border: none;
1497
+ border-radius: 7px; padding: 5px 11px; font-size: 14px; cursor: pointer;
1498
+ }
1499
+ #wAddBtn:hover { filter: brightness(1.1); }
1487
1500
  .wl-row {
1488
1501
  display: flex; align-items: baseline; gap: 7px;
1489
1502
  background: var(--panel); border: 1px solid var(--border);