beast-agent 2.3.4 → 2.4.1

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.
@@ -1066,6 +1066,7 @@ async function renderActiveSettingsTab() {
1066
1066
  case 'websearch': await renderWebSearchPane(); break;
1067
1067
  case 'mcp': await renderMcpPane(); break;
1068
1068
  case 'events': await renderEventsPane(); break;
1069
+ case 'empati': await renderEmpatiPane(); break;
1069
1070
  case 'cron': await openCron(); break;
1070
1071
  case 'usage': await renderUsagePane(); break;
1071
1072
  case 'agents': await refreshAgentsPane(); break;
@@ -1151,7 +1152,7 @@ function switchTab(name) {
1151
1152
  document.querySelectorAll('#setTabs .tab').forEach((b) =>
1152
1153
  b.classList.toggle('active', b.dataset.tab === name)
1153
1154
  );
1154
- for (const p of ['lang', 'provider', 'fallout', 'skills', 'agents', 'tts', 'install', 'email', 'integrations', 'websearch', 'mcp', 'events', 'cron', 'usage', 'logs', 'dash', 'sec', 'update']) {
1155
+ for (const p of ['lang', 'provider', 'fallout', 'skills', 'agents', 'tts', 'install', 'email', 'integrations', 'websearch', 'mcp', 'events', 'empati', 'cron', 'usage', 'logs', 'dash', 'sec', 'update']) {
1155
1156
  const el = $('#tab-' + p);
1156
1157
  if (el) el.hidden = p !== name; // guard: eksik pane tüm sekmeleri kilitlemesin
1157
1158
  }
@@ -1160,6 +1161,7 @@ function switchTab(name) {
1160
1161
  if (name === 'usage') renderUsagePane();
1161
1162
  if (name === 'install') renderInstallPane();
1162
1163
  if (name === 'events') renderEventsPane();
1164
+ if (name === 'empati') renderEmpatiPane();
1163
1165
  if (name === 'logs') renderLogPane();
1164
1166
  if (name === 'dash') renderDashboardPane();
1165
1167
  if (name === 'sec') renderSecurityPane();
@@ -2331,47 +2333,49 @@ async function renderEmailPane() {
2331
2333
  const PASS_MASK = '••••••••';
2332
2334
  let savedPass = '';
2333
2335
  let passChanged = false;
2336
+ /* ID'ler mail* önekiyle: empati sekmesindeki em* ID'leriyle ÇAKIŞMASIN —
2337
+ aynı ID iki panelde olursa $() ilkini bulur, empati butonları ölü kalırdı */
2334
2338
  pane.innerHTML =
2335
- '<h2>' + _t('em_h2') + '</h2><div class="sub">' + _t('em_sub') + '</div>' +
2339
+ '<h2>' + _t('mail_h2') + '</h2><div class="sub">' + _t('mail_sub') + '</div>' +
2336
2340
  `<div class="form-grid" style="grid-template-columns:1fr 1fr;align-items:center;margin-top:10px">
2337
- <input id="emHost" class="inp" placeholder="imap.gmail.com" autocomplete="off" />
2338
- <input id="emUser" class="inp" placeholder="adres@gmail.com" autocomplete="off" />
2339
- <input id="emPass" class="inp" type="password" placeholder="Uygulama Şifresi" autocomplete="off" />
2340
- <input id="emSmtpHost" class="inp" placeholder="smtp.gmail.com" autocomplete="off" />
2341
- <input id="emSmtpPort" class="inp" placeholder="465" autocomplete="off" />
2342
- <button id="emSave" class="btn ghost">${_t('em_save')}</button>
2341
+ <input id="mailHost" class="inp" placeholder="imap.gmail.com" autocomplete="off" />
2342
+ <input id="mailUser" class="inp" placeholder="adres@gmail.com" autocomplete="off" />
2343
+ <input id="mailPass" class="inp" type="password" placeholder="Uygulama Şifresi" autocomplete="off" />
2344
+ <input id="mailSmtpHost" class="inp" placeholder="smtp.gmail.com" autocomplete="off" />
2345
+ <input id="mailSmtpPort" class="inp" placeholder="465" autocomplete="off" />
2346
+ <button id="mailSave" class="btn ghost">${_t('mail_save')}</button>
2343
2347
  </div>` +
2344
- '<div class="sub" style="margin-top:8px">' + _t('em_note') + '</div>';
2348
+ '<div class="sub" style="margin-top:8px">' + _t('mail_note') + '</div>';
2345
2349
 
2346
2350
  try {
2347
2351
  const em = await beast.getEmail();
2348
- $('#emHost').value = em.host || 'imap.gmail.com';
2349
- $('#emUser').value = em.user || '';
2350
- $('#emSmtpHost').value = em.smtpHost || 'smtp.gmail.com';
2351
- $('#emSmtpPort').value = em.smtpPort || 465;
2352
+ $('#mailHost').value = em.host || 'imap.gmail.com';
2353
+ $('#mailUser').value = em.user || '';
2354
+ $('#mailSmtpHost').value = em.smtpHost || 'smtp.gmail.com';
2355
+ $('#mailSmtpPort').value = em.smtpPort || 465;
2352
2356
  savedPass = em.pass || '';
2353
2357
  } catch {}
2354
- const emPass = $('#emPass');
2358
+ const mailPass = $('#mailPass');
2355
2359
  if (savedPass) {
2356
2360
  /* kayıtlı şifre maskelenir; sadece kullanıcı değiştirirse yenisi gönderilir */
2357
- emPass.value = PASS_MASK;
2358
- emPass.placeholder = window.I18N ? window.I18N.t('em_pass_masked') : 'Mevcut şifre korunuyor — değiştirmek için yeniden gir';
2361
+ mailPass.value = PASS_MASK;
2362
+ mailPass.placeholder = window.I18N ? window.I18N.t('mail_pass_masked') : 'Mevcut şifre korunuyor — değiştirmek için yeniden gir';
2359
2363
  }
2360
- emPass.addEventListener('focus', () => { if (emPass.value === PASS_MASK) emPass.value = ''; });
2361
- emPass.addEventListener('input', () => { passChanged = true; });
2362
- $('#emSave').addEventListener('click', async () => {
2363
- const entered = emPass.value;
2364
+ mailPass.addEventListener('focus', () => { if (mailPass.value === PASS_MASK) mailPass.value = ''; });
2365
+ mailPass.addEventListener('input', () => { passChanged = true; });
2366
+ $('#mailSave').addEventListener('click', async () => {
2367
+ const entered = mailPass.value;
2364
2368
  const pass = passChanged ? entered : savedPass;
2365
2369
  await beast.setEmail({
2366
- host: $('#emHost').value.trim() || 'imap.gmail.com',
2370
+ host: $('#mailHost').value.trim() || 'imap.gmail.com',
2367
2371
  port: 993,
2368
- user: $('#emUser').value.trim(),
2372
+ user: $('#mailUser').value.trim(),
2369
2373
  pass,
2370
- smtpHost: $('#emSmtpHost').value.trim() || 'smtp.gmail.com',
2371
- smtpPort: Number($('#emSmtpPort').value) || 465,
2374
+ smtpHost: $('#mailSmtpHost').value.trim() || 'smtp.gmail.com',
2375
+ smtpPort: Number($('#mailSmtpPort').value) || 465,
2372
2376
  });
2373
- if (pass && pass !== PASS_MASK) { savedPass = pass; emPass.value = PASS_MASK; passChanged = false; }
2374
- toast(_t('em_saved'));
2377
+ if (pass && pass !== PASS_MASK) { savedPass = pass; mailPass.value = PASS_MASK; passChanged = false; }
2378
+ toast(_t('mail_saved'));
2375
2379
  });
2376
2380
  }
2377
2381
 
@@ -3022,6 +3026,181 @@ async function renderEventsPane() {
3022
3026
  });
3023
3027
  }
3024
3028
 
3029
+ /* ---------------- EMPATİ LOOP (proaktif algı sekmesi) ---------------- */
3030
+ async function renderEmpatiPane() {
3031
+ const pane = $('#tab-empati');
3032
+ if (!pane) return;
3033
+ /* pane-scope: e-posta sekmesinde eskiden aynı ID'ler vardı; $() global arar
3034
+ ve YANLIŞ butona bağlardı — tüm aramalar bu panelin içinde kalır */
3035
+ const q = (sel) => pane.querySelector(sel);
3036
+ const cfg = await beast.empatiGet().catch(() => null);
3037
+ let events = [];
3038
+ try { events = await beast.empatiEvents(); } catch {}
3039
+ let mem = null;
3040
+ try { mem = await beast.empatiMemory(); } catch {}
3041
+ let models = [];
3042
+ try { models = (await beast.getState()).models || []; } catch {}
3043
+ if (!cfg) {
3044
+ pane.innerHTML = '<h2>' + _t('em_h2') + '</h2><div class="sub">' + _t('em_ipc_err') + '</div>';
3045
+ return;
3046
+ }
3047
+ const modelOpts =
3048
+ '<option value="">' + _t('em_model_main') + '</option>' +
3049
+ models.map((m) =>
3050
+ '<option value="' + escapeHtml(m.sel || '') + '"' + (cfg.filterModel === m.sel ? ' selected' : '') + '>' +
3051
+ escapeHtml((m.providerName || '') + ' · ' + (m.model || '')) + '</option>'
3052
+ ).join('');
3053
+ const targetOpts = [
3054
+ ['whatsapp', _t('em_notify_wa')],
3055
+ ['telegram', _t('em_notify_tg')],
3056
+ ['discord', _t('em_notify_dc')],
3057
+ ].map(([v, lbl]) =>
3058
+ '<option value="' + v + '"' + (cfg.notifyTarget === v ? ' selected' : '') + '>' + escapeHtml(lbl) + '</option>'
3059
+ ).join('');
3060
+ const when = (iso) => {
3061
+ try {
3062
+ return new Date(iso).toLocaleString('tr-TR', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' });
3063
+ } catch { return String(iso || ''); }
3064
+ };
3065
+ const statusT = { notified: _t('em_st_notified'), queued: _t('em_st_queued'), stored: _t('em_st_stored'), ignored: _t('em_st_ignored') };
3066
+ const lvBadge = (e) => {
3067
+ if (!e.level) return '';
3068
+ const color = e.level === 'high' ? '#e06c75' : '#d9a441';
3069
+ return ' · <b style="color:' + color + '">' + (e.level === 'high' ? _t('em_lv_high') : _t('em_lv_medium')) + '</b>';
3070
+ };
3071
+ let rows = '';
3072
+ for (const e of events) {
3073
+ rows +=
3074
+ '<div class="usage-row" style="align-items:flex-start">' +
3075
+ '<div style="min-width:0;flex:1">' +
3076
+ '<div style="font-size:12px">' + escapeHtml(e.title || '') + '</div>' +
3077
+ '<div class="ur-meta">' + escapeHtml(e.source || '') + ' · ' + when(e.ts) + ' · %' + (e.priority || 0) +
3078
+ (e.reason ? ' · ' + escapeHtml(e.reason) : '') + '</div>' +
3079
+ (e.text ? '<div class="sub" style="font-size:11px">' + escapeHtml(e.text) + '</div>' : '') +
3080
+ '</div>' +
3081
+ '<span style="flex:none;font-size:11px;color:var(--muted)">' + (statusT[e.status] || e.status) + lvBadge(e) + '</span>' +
3082
+ '</div>';
3083
+ }
3084
+
3085
+ pane.innerHTML =
3086
+ '<h2>' + _t('em_h2') + '</h2>' +
3087
+ '<div class="sub">' + _t('em_sub') + '</div>' +
3088
+ (cfg.lastRunAt ? '<div class="sub">' + _t('em_lastrun') + ': ' + when(cfg.lastRunAt) + (cfg.running ? ' · ⏳' : '') + '</div>' : '') +
3089
+ `<div class="fo-toggles" style="margin-top:10px">
3090
+ <label class="lock-row"><input type="checkbox" id="emOn" ${cfg.enabled ? 'checked' : ''}/><span>${_t('em_on')}</span></label>
3091
+ </div>
3092
+ <div id="emDetail" style="${cfg.enabled ? '' : 'opacity:.5'}">
3093
+ <div class="form-grid" style="grid-template-columns:1fr 1fr 1fr;margin-top:8px">
3094
+ <div><label class="sub">${_t('em_interval')}</label><input id="emInterval" class="inp" type="number" min="3" max="1440" value="${cfg.intervalMin}"/></div>
3095
+ <div><label class="sub">${_t('em_min_notify')}</label><input id="emMin" class="inp" type="number" min="0" max="100" value="${cfg.minNotifyPriority}"/></div>
3096
+ <div><label class="sub">${_t('em_cooldown')}</label><input id="emCd" class="inp" type="number" min="0" max="10080" value="${cfg.cooldownMin}"/></div>
3097
+ </div>
3098
+ <div style="margin-top:8px">
3099
+ <label class="sub">${_t('em_model')}</label>
3100
+ <select id="emModel" class="inp">${modelOpts}</select>
3101
+ <div class="sub">${_t('em_model_sub')}</div>
3102
+ </div>
3103
+ <div style="margin-top:8px">
3104
+ <label class="sub">${_t('em_notify')}</label>
3105
+ <select id="emTarget" class="inp"><option value="">${_t('em_notify_auto')}</option>${targetOpts}</select>
3106
+ <div class="sub">${_t('em_notify_sub')}</div>
3107
+ </div>
3108
+ <div style="margin-top:8px">
3109
+ <label class="sub">${_t('em_interests')}</label>
3110
+ <textarea id="emInterests" class="inp" rows="2" placeholder="${_t('em_interests_ph')}">${escapeHtml(cfg.interests || '')}</textarea>
3111
+ <div class="sub">${_t('em_interests_auto')}</div>
3112
+ </div>
3113
+ <label class="lock-row" style="margin-top:8px"><input type="checkbox" id="emNews" ${cfg.newsTopics ? 'checked' : ''}/><span>${_t('em_news')}</span></label>
3114
+ <input id="emTopics" class="inp" style="${cfg.newsTopics ? '' : 'opacity:.5'}" placeholder="${_t('em_news_topics_ph')}" value="${escapeHtml(cfg.newsTopics || '')}" spellcheck="false"/>
3115
+ <div style="display:flex;gap:8px;margin-top:10px;align-items:center">
3116
+ <button id="emSave" class="btn">${_t('mcp_save')}</button>
3117
+ <button id="emScan" class="btn ghost">${_t('em_scan')}</button>
3118
+ <span id="emMsg" class="sub" style="margin:0"></span>
3119
+ </div>
3120
+ <div class="sub" style="margin-top:6px">${_t('em_note')}</div>
3121
+ </div>
3122
+ <h3 style="margin-top:16px;color:var(--muted)">${_t('em_mem_h2')}</h3>
3123
+ <div class="sub">${_t('em_mem_sub')}</div>
3124
+ <div style="margin-top:6px"><label class="sub">${_t('em_mem_learned')}</label>${(() => {
3125
+ const learned = (mem && Array.isArray(mem.learned)) ? mem.learned : [];
3126
+ if (!learned.length) return '<div class="sub" style="margin-top:2px">' + _t('em_mem_no_learned') + '</div>';
3127
+ return '<div style="display:flex;flex-wrap:wrap;gap:4px;margin-top:4px">' +
3128
+ learned.map((x) =>
3129
+ '<span style="font-size:11px;padding:2px 8px;border:1px solid var(--border);border-radius:10px">' +
3130
+ escapeHtml(x.w) + ' <span style="color:var(--muted)">×' + (x.c || 0) + '</span></span>'
3131
+ ).join('') + '</div>';
3132
+ })()}</div>
3133
+ <div style="margin-top:8px"><label class="sub">${_t('em_mem_recent')}</label>${(() => {
3134
+ const chats = (mem && Array.isArray(mem.chats)) ? mem.chats : [];
3135
+ if (!chats.length) return '<div class="sub" style="margin-top:2px">' + _t('em_mem_no_recent') + '</div>';
3136
+ return '<div style="margin-top:4px">' + chats.map((c) =>
3137
+ '<div class="usage-row" style="align-items:flex-start">' +
3138
+ '<div style="min-width:0;flex:1">' +
3139
+ '<div style="font-size:12px">' + escapeHtml(c.t || '') + '</div>' +
3140
+ '<div class="ur-meta">' + escapeHtml(c.ch || '') + ' · ' + when(c.ts) + '</div>' +
3141
+ '</div></div>').join('') + '</div>';
3142
+ })()}
3143
+ <button id="emMemClear" class="btn ghost" style="margin-top:8px;font-size:12px">${_t('em_mem_clear')}</button>
3144
+ </div>
3145
+ <h3 style="margin-top:16px;color:var(--muted)">${_t('em_events')}</h3>`;
3146
+ const wrap = document.createElement('div');
3147
+ if (!rows) wrap.innerHTML = '<p class="sub">' + _t('em_no_events') + '</p>';
3148
+ else wrap.innerHTML = rows;
3149
+ pane.appendChild(wrap);
3150
+
3151
+ const emOn = q('#emOn');
3152
+ const emDetail = q('#emDetail');
3153
+ const emNews = q('#emNews');
3154
+ const emTopics = q('#emTopics');
3155
+ if (emOn && emDetail) emOn.addEventListener('change', () => { emDetail.style.opacity = emOn.checked ? '' : '.5'; });
3156
+ if (emNews && emTopics) emNews.addEventListener('change', () => { emTopics.style.opacity = emNews.checked ? '' : '.5'; });
3157
+ const emSaveBtn = q('#emSave');
3158
+ if (emSaveBtn) emSaveBtn.addEventListener('click', async () => {
3159
+ const patch = {
3160
+ enabled: emOn.checked,
3161
+ intervalMin: Number(q('#emInterval').value) || cfg.intervalMin,
3162
+ minNotifyPriority: Number(q('#emMin').value),
3163
+ cooldownMin: Number(q('#emCd').value),
3164
+ notifyTarget: q('#emTarget') ? q('#emTarget').value : '',
3165
+ filterModel: q('#emModel') ? q('#emModel').value : '',
3166
+ interests: q('#emInterests').value.trim(),
3167
+ newsTopics: emNews.checked ? emTopics.value.trim() : '',
3168
+ };
3169
+ const r = await beast.empatiSet(patch).catch(() => null);
3170
+ toast(r ? _t('em_saved') : 'Hata');
3171
+ if (r) renderEmpatiPane();
3172
+ });
3173
+ const emScanBtn = q('#emScan');
3174
+ if (emScanBtn) emScanBtn.addEventListener('click', async () => {
3175
+ emScanBtn.disabled = true; /* çift tık → 'döngü zaten çalışıyor' kilitlenmesin */
3176
+ const msg = q('#emMsg');
3177
+ if (msg) msg.textContent = '⏳';
3178
+ const r = await beast.empatiScan().catch(() => null);
3179
+ if (r && r.ok) {
3180
+ const txt = _t('em_scan_ok')
3181
+ .replace('{raw}', String(r.raw || 0))
3182
+ .replace('{queued}', String(r.queued || 0))
3183
+ .replace('{stored}', String(r.stored || 0));
3184
+ toast(txt);
3185
+ await renderEmpatiPane(); /* olay + hafıza listeleri tazelensin */
3186
+ const m2 = q('#emMsg');
3187
+ if (m2) m2.textContent = txt; /* yeniden çizim mesajı silmesin */
3188
+ } else {
3189
+ const err = (r && r.error) || 'hata';
3190
+ if (msg) msg.textContent = /zaten çalışıyor/.test(err) ? _t('em_scan_busy') : err;
3191
+ toast('Tarama hatası: ' + err);
3192
+ }
3193
+ const b2 = q('#emScan');
3194
+ if (b2) b2.disabled = false;
3195
+ });
3196
+ const emMemClearBtn = q('#emMemClear');
3197
+ if (emMemClearBtn) emMemClearBtn.addEventListener('click', async () => {
3198
+ const r = await beast.empatiMemClear().catch(() => null);
3199
+ toast(r && r.ok ? _t('em_mem_cleared') : 'Hata');
3200
+ if (r && r.ok) renderEmpatiPane();
3201
+ });
3202
+ }
3203
+
3025
3204
  async function renderWaAllow() {
3026
3205
  const wrap = $('#waAllowChips');
3027
3206
  if (!wrap) return;
@@ -4706,6 +4885,15 @@ function onEvent(ev) {
4706
4885
  return;
4707
4886
  }
4708
4887
  if (ev.type === 'install-progress') { updateInstallPct(ev); return; }
4888
+ /* EMPATİ LOOP: proaktif bildirim (masaüstü) + sekme canlı yenileme */
4889
+ if (ev.type === 'proactive') {
4890
+ toast('🫡 ' + (ev.text || ev.title || ''));
4891
+ return;
4892
+ }
4893
+ if (ev.type === 'empati') {
4894
+ if (!els.settingsOverlay.hidden && setTab === 'empati') renderEmpatiPane();
4895
+ return;
4896
+ }
4709
4897
  if (ev.type === 'update') {
4710
4898
  if (ev.downloaded) toast(_t('up_downloaded') + ' (v' + (ev.version || '?') + ') — /update now');
4711
4899
  else if (ev.available && ev.version && ev.version !== ev.current) toast(_t('up_available') + ' (v' + ev.version + ')');