beast-agent 2.3.3 → 2.4.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.
@@ -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();
@@ -2146,13 +2148,26 @@ async function renderInstallPane() {
2146
2148
  if (!el) return;
2147
2149
  if (!rows || !rows.length) { el.innerHTML = '<div class="sub">Durum alınamadı</div>'; return; }
2148
2150
  autoStart(rows);
2151
+ const hasPct = (r) => typeof r.pct === 'number' && ['missing', 'partial', 'loading'].includes(r.state);
2152
+ const pctLabel = (r) => {
2153
+ if (!hasPct(r)) return '<span id="inst-pct-' + r.id + '" style="display:none"></span>';
2154
+ const mb = (r.loadedMb && r.totalMb) ? ' · ' + r.loadedMb + '/' + r.totalMb + ' MB' : '';
2155
+ return '<span id="inst-pct-' + r.id + '" style="color:#d9a441;font-weight:700;font-size:11px;flex:none">%' + Math.round(r.pct) + mb + '</span>';
2156
+ };
2157
+ const bar = (r) => {
2158
+ if (!hasPct(r)) return '<div id="inst-bar-' + r.id + '" style="display:none"></div>';
2159
+ return '<div id="inst-bar-' + r.id + '" style="margin-top:7px;height:6px;border-radius:3px;background:var(--border);overflow:hidden">' +
2160
+ '<div id="inst-fill-' + r.id + '" style="height:100%;width:' + Math.max(2, Math.round(r.pct)) + '%;background:#d9a441;border-radius:3px;transition:width .4s ease"></div></div>';
2161
+ };
2149
2162
  el.innerHTML = rows.map((r) => (
2150
2163
  '<div style="display:flex;align-items:center;gap:10px;padding:9px 0;border-bottom:1px solid var(--border)">' +
2151
2164
  '<div style="flex:1;min-width:0">' +
2152
2165
  '<div style="font-size:13px">' + escapeHtml(r.name) + '</div>' +
2153
2166
  '<div class="sub" style="font-size:11px">' + escapeHtml(r.detail || '') + (r.mb ? ' · ' + r.mb + ' MB' : '') + '</div>' +
2167
+ bar(r) +
2154
2168
  '</div>' +
2155
2169
  badge(r.state) +
2170
+ pctLabel(r) +
2156
2171
  '</div>'
2157
2172
  )).join('');
2158
2173
  };
@@ -2168,6 +2183,26 @@ async function renderInstallPane() {
2168
2183
  refreshTimer = setInterval(refresh, 4000);
2169
2184
  }
2170
2185
 
2186
+ /* Kurulum progress event'i: sekme açıksa bar'ı anında güncelle (4 sn polling beklemeden).
2187
+ Sekme kapalıysa yok say — açılınca install:status zaten kayıtlı yüzeyi getirir. */
2188
+ function updateInstallPct(ev) {
2189
+ try {
2190
+ if (!ev || !ev.id) return;
2191
+ const pane = document.getElementById('tab-install');
2192
+ if (!pane || pane.hidden) return;
2193
+ const bar = document.getElementById('inst-bar-' + ev.id);
2194
+ const fill = document.getElementById('inst-fill-' + ev.id);
2195
+ const label = document.getElementById('inst-pct-' + ev.id);
2196
+ if (!bar || !fill || !label) return;
2197
+ const pct = Math.max(0, Math.min(100, Math.round(ev.pct || 0)));
2198
+ const mb = (ev.loaded && ev.total) ? ' · ' + Math.round(ev.loaded / 1048576) + '/' + Math.round(ev.total / 1048576) + ' MB' : '';
2199
+ bar.style.display = '';
2200
+ fill.style.width = Math.max(2, pct) + '%';
2201
+ label.style.display = '';
2202
+ label.textContent = '%' + pct + mb;
2203
+ } catch {}
2204
+ }
2205
+
2171
2206
  async function renderTtsPane() { const pane = $('#tab-tts');
2172
2207
  if (!pane) return;
2173
2208
  /* STT DURUMU: model indirildi mi / iniyor mu / hangi motor */
@@ -2989,6 +3024,136 @@ async function renderEventsPane() {
2989
3024
  });
2990
3025
  }
2991
3026
 
3027
+ /* ---------------- EMPATİ LOOP (proaktif algı sekmesi) ---------------- */
3028
+ async function renderEmpatiPane() {
3029
+ const pane = $('#tab-empati');
3030
+ if (!pane) return;
3031
+ const cfg = await beast.empatiGet().catch(() => null);
3032
+ let events = [];
3033
+ try { events = await beast.empatiEvents(); } catch {}
3034
+ let models = [];
3035
+ try { models = (await beast.getState()).models || []; } catch {}
3036
+ if (!cfg) {
3037
+ pane.innerHTML = '<h2>' + _t('em_h2') + '</h2><div class="sub">' + _t('em_ipc_err') + '</div>';
3038
+ return;
3039
+ }
3040
+ const modelOpts =
3041
+ '<option value="">' + _t('em_model_main') + '</option>' +
3042
+ models.map((m) =>
3043
+ '<option value="' + escapeHtml(m.sel || '') + '"' + (cfg.filterModel === m.sel ? ' selected' : '') + '>' +
3044
+ escapeHtml((m.providerName || '') + ' · ' + (m.model || '')) + '</option>'
3045
+ ).join('');
3046
+ const targetOpts = [
3047
+ ['whatsapp', _t('em_notify_wa')],
3048
+ ['telegram', _t('em_notify_tg')],
3049
+ ['discord', _t('em_notify_dc')],
3050
+ ].map(([v, lbl]) =>
3051
+ '<option value="' + v + '"' + (cfg.notifyTarget === v ? ' selected' : '') + '>' + escapeHtml(lbl) + '</option>'
3052
+ ).join('');
3053
+ const when = (iso) => {
3054
+ try {
3055
+ return new Date(iso).toLocaleString('tr-TR', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' });
3056
+ } catch { return String(iso || ''); }
3057
+ };
3058
+ const statusT = { notified: _t('em_st_notified'), queued: _t('em_st_queued'), stored: _t('em_st_stored'), ignored: _t('em_st_ignored') };
3059
+ const lvBadge = (e) => {
3060
+ if (!e.level) return '';
3061
+ const color = e.level === 'high' ? '#e06c75' : '#d9a441';
3062
+ return ' · <b style="color:' + color + '">' + (e.level === 'high' ? _t('em_lv_high') : _t('em_lv_medium')) + '</b>';
3063
+ };
3064
+ let rows = '';
3065
+ for (const e of events) {
3066
+ rows +=
3067
+ '<div class="usage-row" style="align-items:flex-start">' +
3068
+ '<div style="min-width:0;flex:1">' +
3069
+ '<div style="font-size:12px">' + escapeHtml(e.title || '') + '</div>' +
3070
+ '<div class="ur-meta">' + escapeHtml(e.source || '') + ' · ' + when(e.ts) + ' · %' + (e.priority || 0) +
3071
+ (e.reason ? ' · ' + escapeHtml(e.reason) : '') + '</div>' +
3072
+ (e.text ? '<div class="sub" style="font-size:11px">' + escapeHtml(e.text) + '</div>' : '') +
3073
+ '</div>' +
3074
+ '<span style="flex:none;font-size:11px;color:var(--muted)">' + (statusT[e.status] || e.status) + lvBadge(e) + '</span>' +
3075
+ '</div>';
3076
+ }
3077
+
3078
+ pane.innerHTML =
3079
+ '<h2>' + _t('em_h2') + '</h2>' +
3080
+ '<div class="sub">' + _t('em_sub') + '</div>' +
3081
+ (cfg.lastRunAt ? '<div class="sub">' + _t('em_lastrun') + ': ' + when(cfg.lastRunAt) + (cfg.running ? ' · ⏳' : '') + '</div>' : '') +
3082
+ `<div class="fo-toggles" style="margin-top:10px">
3083
+ <label class="lock-row"><input type="checkbox" id="emOn" ${cfg.enabled ? 'checked' : ''}/><span>${_t('em_on')}</span></label>
3084
+ </div>
3085
+ <div id="emDetail" style="${cfg.enabled ? '' : 'opacity:.5'}">
3086
+ <div class="form-grid" style="grid-template-columns:1fr 1fr 1fr;margin-top:8px">
3087
+ <div><label class="sub">${_t('em_interval')}</label><input id="emInterval" class="inp" type="number" min="3" max="1440" value="${cfg.intervalMin}"/></div>
3088
+ <div><label class="sub">${_t('em_min_notify')}</label><input id="emMin" class="inp" type="number" min="0" max="100" value="${cfg.minNotifyPriority}"/></div>
3089
+ <div><label class="sub">${_t('em_cooldown')}</label><input id="emCd" class="inp" type="number" min="0" max="10080" value="${cfg.cooldownMin}"/></div>
3090
+ </div>
3091
+ <div style="margin-top:8px">
3092
+ <label class="sub">${_t('em_model')}</label>
3093
+ <select id="emModel" class="inp">${modelOpts}</select>
3094
+ <div class="sub">${_t('em_model_sub')}</div>
3095
+ </div>
3096
+ <div style="margin-top:8px">
3097
+ <label class="sub">${_t('em_notify')}</label>
3098
+ <select id="emTarget" class="inp"><option value="">${_t('em_notify_auto')}</option>${targetOpts}</select>
3099
+ <div class="sub">${_t('em_notify_sub')}</div>
3100
+ </div>
3101
+ <div style="margin-top:8px">
3102
+ <label class="sub">${_t('em_interests')}</label>
3103
+ <textarea id="emInterests" class="inp" rows="2" placeholder="${_t('em_interests_ph')}">${escapeHtml(cfg.interests || '')}</textarea>
3104
+ </div>
3105
+ <label class="lock-row" style="margin-top:8px"><input type="checkbox" id="emNews" ${cfg.newsTopics ? 'checked' : ''}/><span>${_t('em_news')}</span></label>
3106
+ <input id="emTopics" class="inp" style="${cfg.newsTopics ? '' : 'opacity:.5'}" placeholder="${_t('em_news_topics_ph')}" value="${escapeHtml(cfg.newsTopics || '')}" spellcheck="false"/>
3107
+ <div style="display:flex;gap:8px;margin-top:10px;align-items:center">
3108
+ <button id="emSave" class="btn">${_t('mcp_save')}</button>
3109
+ <button id="emScan" class="btn ghost">${_t('em_scan')}</button>
3110
+ <span id="emMsg" class="sub" style="margin:0"></span>
3111
+ </div>
3112
+ <div class="sub" style="margin-top:6px">${_t('em_note')}</div>
3113
+ </div>
3114
+ <h3 style="margin-top:16px;color:var(--muted)">${_t('em_events')}</h3>`;
3115
+ const wrap = document.createElement('div');
3116
+ if (!rows) wrap.innerHTML = '<p class="sub">' + _t('em_no_events') + '</p>';
3117
+ else wrap.innerHTML = rows;
3118
+ pane.appendChild(wrap);
3119
+
3120
+ const emOn = $('#emOn');
3121
+ const emDetail = $('#emDetail');
3122
+ const emNews = $('#emNews');
3123
+ const emTopics = $('#emTopics');
3124
+ emOn.addEventListener('change', () => { emDetail.style.opacity = emOn.checked ? '' : '.5'; });
3125
+ emNews.addEventListener('change', () => { emTopics.style.opacity = emNews.checked ? '' : '.5'; });
3126
+ $('#emSave').addEventListener('click', async () => {
3127
+ const patch = {
3128
+ enabled: emOn.checked,
3129
+ intervalMin: Number($('#emInterval').value) || cfg.intervalMin,
3130
+ minNotifyPriority: Number($('#emMin').value),
3131
+ cooldownMin: Number($('#emCd').value),
3132
+ notifyTarget: $('#emTarget') ? $('#emTarget').value : '',
3133
+ filterModel: $('#emModel') ? $('#emModel').value : '',
3134
+ interests: $('#emInterests').value.trim(),
3135
+ newsTopics: emNews.checked ? emTopics.value.trim() : '',
3136
+ };
3137
+ const r = await beast.empatiSet(patch).catch(() => null);
3138
+ toast(r ? _t('em_saved') : 'Hata');
3139
+ if (r) renderEmpatiPane();
3140
+ });
3141
+ $('#emScan').addEventListener('click', async () => {
3142
+ const msg = $('#emMsg');
3143
+ msg.textContent = '⏳';
3144
+ const r = await beast.empatiScan().catch(() => null);
3145
+ if (r && r.ok) {
3146
+ msg.textContent = _t('em_scan_ok')
3147
+ .replace('{raw}', String(r.raw || 0))
3148
+ .replace('{queued}', String(r.queued || 0))
3149
+ .replace('{stored}', String(r.stored || 0));
3150
+ renderEmpatiPane();
3151
+ } else {
3152
+ msg.textContent = (r && r.error) || 'hata';
3153
+ }
3154
+ });
3155
+ }
3156
+
2992
3157
  async function renderWaAllow() {
2993
3158
  const wrap = $('#waAllowChips');
2994
3159
  if (!wrap) return;
@@ -4672,6 +4837,16 @@ function onEvent(ev) {
4672
4837
  showApprovalCard(ev);
4673
4838
  return;
4674
4839
  }
4840
+ if (ev.type === 'install-progress') { updateInstallPct(ev); return; }
4841
+ /* EMPATİ LOOP: proaktif bildirim (masaüstü) + sekme canlı yenileme */
4842
+ if (ev.type === 'proactive') {
4843
+ toast('🫡 ' + (ev.text || ev.title || ''));
4844
+ return;
4845
+ }
4846
+ if (ev.type === 'empati') {
4847
+ if (!els.settingsOverlay.hidden && setTab === 'empati') renderEmpatiPane();
4848
+ return;
4849
+ }
4675
4850
  if (ev.type === 'update') {
4676
4851
  if (ev.downloaded) toast(_t('up_downloaded') + ' (v' + (ev.version || '?') + ') — /update now');
4677
4852
  else if (ev.available && ev.version && ev.version !== ev.current) toast(_t('up_available') + ' (v' + ev.version + ')');