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.
@@ -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);