beast-agent 0.25.6 → 0.25.8
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 +1 -1
- package/src/agent/bots.js +8 -0
- package/src/agent/discord.js +234 -0
- package/src/agent/engine.js +63 -31
- package/src/main.js +312 -4
- package/src/preload.js +8 -0
- package/src/renderer/i18n.js +18 -0
- package/src/renderer/renderer.js +339 -29
- package/tests/bg-jobs.test.js +5 -10
package/src/renderer/renderer.js
CHANGED
|
@@ -471,16 +471,21 @@ async function renderSessions(list) {
|
|
|
471
471
|
tgSet = new Set(await beast.tgListSessions());
|
|
472
472
|
} catch {}
|
|
473
473
|
els.sessList.innerHTML = '';
|
|
474
|
-
/* aktif botun oturumları — botlar arası geçişte liste de o bota göre değişir
|
|
474
|
+
/* aktif botun oturumları — botlar arası geçişte liste de o bota göre değişir.
|
|
475
|
+
BOT BAĞLAMA MİGRASYONU: bot kaydı olmayan (eski) oturumlar HER botun
|
|
476
|
+
listesinde de görünür — kullanıcı botunda açıp ilk mesajı yazınca
|
|
477
|
+
oturum KALICI olarak o bota bağlanır (agent:send auto-bind; notlar dahil).
|
|
478
|
+
Başka bir bota AÇIKÇA bağlı olanlar yalnız o botun listesinde. */
|
|
475
479
|
for (const s of list) {
|
|
476
|
-
if (
|
|
480
|
+
if (s.botId && s.botId !== activeBotId) continue;
|
|
481
|
+
const unbound = !s.botId && activeBotId !== 'beast';
|
|
477
482
|
const row = document.createElement('div');
|
|
478
483
|
row.className = 'sess' + (s.id === activeId ? ' active' : '');
|
|
479
484
|
row.innerHTML =
|
|
480
485
|
(waSet.has(s.id) ? '<span class="sess-wa" title="WhatsApp">W</span>' : '') +
|
|
481
486
|
(tgSet.has(s.id) ? '<span class="sess-tg" title="Telegram">T</span>' : '') +
|
|
482
487
|
`<span class="sess-title">${escapeHtml(s.title || 'Yeni Sohbet')}</span>` +
|
|
483
|
-
`<span class="sess-code" title="Oturum kodu">${escapeHtml(s.code || '')}</span>` +
|
|
488
|
+
`<span class="sess-code" title="${unbound ? 'bağlantı bekliyor — ilk mesajda bu bota bağlanır' : 'Oturum kodu'}">${escapeHtml(s.code || '')}${unbound ? ' ·?' : ''}</span>` +
|
|
484
489
|
`<button class="sess-del" title="Sil">×</button>`;
|
|
485
490
|
row.addEventListener('click', () => openSession(s.id));
|
|
486
491
|
row.querySelector('.sess-del').addEventListener('click', async (e) => {
|
|
@@ -815,7 +820,7 @@ async function renderProviderPane() {
|
|
|
815
820
|
|
|
816
821
|
for (const m of state.models || []) {
|
|
817
822
|
const row = document.createElement('div');
|
|
818
|
-
row.className = 'prov-row' + (
|
|
823
|
+
row.className = 'prov-row' + (effectiveModelSel() === m.sel ? ' active' : '');
|
|
819
824
|
row.innerHTML =
|
|
820
825
|
`<span class="prov-name">${escapeHtml(m.providerName)}</span>` +
|
|
821
826
|
`<span class="prov-model">${escapeHtml(m.model)}</span>` +
|
|
@@ -823,6 +828,7 @@ async function renderProviderPane() {
|
|
|
823
828
|
`<button class="prov-del" title="${_t('p_del_title')}">✕</button>`;
|
|
824
829
|
row.addEventListener('click', async () => {
|
|
825
830
|
state = await beast.setModel(m.sel);
|
|
831
|
+
await refreshBots().catch(() => {});
|
|
826
832
|
applyState();
|
|
827
833
|
renderProviderPane();
|
|
828
834
|
toast(_t('p_model_toast') + m.providerName + ' · ' + m.model);
|
|
@@ -1535,6 +1541,35 @@ async function renderIntegrationsPane() {
|
|
|
1535
1541
|
<button id="tgAllowAdd" class="btn ghost" style="margin-top:6px">${_t('it_add')}</button>
|
|
1536
1542
|
</div>
|
|
1537
1543
|
<div class="sub" style="margin-top:8px">${_t('it_tg_note')}</div>
|
|
1544
|
+
</div>
|
|
1545
|
+
|
|
1546
|
+
<div class="wa-card" style="margin-top:14px">
|
|
1547
|
+
<div class="wa-head">
|
|
1548
|
+
<div class="wa-logo tg">D</div>
|
|
1549
|
+
<div>
|
|
1550
|
+
<div class="wa-title">Discord</div>
|
|
1551
|
+
<div class="wa-sub">${_t('it_dc_sub')}</div>
|
|
1552
|
+
</div>
|
|
1553
|
+
</div>
|
|
1554
|
+
<div class="wa-status"><span id="dcDot" class="wa-dot"></span><span id="dcStatText">—</span></div>
|
|
1555
|
+
<div id="dcUser" class="wa-user" hidden></div>
|
|
1556
|
+
<div class="form-grid" style="grid-template-columns:1fr auto;align-items:center">
|
|
1557
|
+
<input id="dcTokenInp" class="inp" type="password" style="margin:6px 0 0" placeholder="${_t('it_dc_token_ph')}" autocomplete="off" />
|
|
1558
|
+
<button id="dcSaveBtn" class="btn" style="margin-top:6px">${_t('it_tg_save')}</button>
|
|
1559
|
+
</div>
|
|
1560
|
+
<div class="wa-actions" style="margin-top:6px">
|
|
1561
|
+
<button id="dcStopBtn" class="btn ghost">${_t('it_disconnect')}</button>
|
|
1562
|
+
</div>
|
|
1563
|
+
<div class="divider"></div>
|
|
1564
|
+
<label class="mem-label" style="margin-top:0">${_t('it_allow_label')} — Discord</label>
|
|
1565
|
+
<div id="dcAllowChips" class="chips-inline"></div>
|
|
1566
|
+
<div class="form-grid" style="grid-template-columns:1.2fr 1fr 1fr auto;align-items:center">
|
|
1567
|
+
<input id="dcAllowNameInp" class="inp" style="margin:6px 0 0" placeholder="${_t('it_name_ph')}" autocomplete="off" />
|
|
1568
|
+
<input id="dcAllowIdInp" class="inp" style="margin:6px 0 0" placeholder="${_t('it_dc_id_ph')}" autocomplete="off" />
|
|
1569
|
+
<select id="dcAllowBotSel" class="perm-select" style="margin:6px 0 0;min-width:105px" title="${_t('bot_bind_title')}"></select>
|
|
1570
|
+
<button id="dcAllowAdd" class="btn ghost" style="margin-top:6px">${_t('it_add')}</button>
|
|
1571
|
+
</div>
|
|
1572
|
+
<div class="sub" style="margin-top:8px">${_t('it_dc_note')}</div>
|
|
1538
1573
|
</div>`;
|
|
1539
1574
|
|
|
1540
1575
|
const waGroupsOn = $('#waGroupsOn');
|
|
@@ -1597,6 +1632,29 @@ async function renderIntegrationsPane() {
|
|
|
1597
1632
|
tgUI.user = ts.user || null;
|
|
1598
1633
|
updateTgPane();
|
|
1599
1634
|
} catch {}
|
|
1635
|
+
|
|
1636
|
+
/* DISCORD: token + allow list + durum */
|
|
1637
|
+
$('#dcSaveBtn').addEventListener('click', async () => {
|
|
1638
|
+
const tok = $('#dcTokenInp').value.trim();
|
|
1639
|
+
if (!tok) { toast(_t('it_dc_token_ph')); return; }
|
|
1640
|
+
toast(_t('it_dc_connecting'));
|
|
1641
|
+
const r = await beast.dcSetToken(tok).catch((e) => ({ status: 'error', error: String(e) }));
|
|
1642
|
+
$('#dcTokenInp').value = '';
|
|
1643
|
+
updateDcPane();
|
|
1644
|
+
toast(r.status === 'connected' ? 'Discord bağlı: ' + (r.user || '') : r.status === 'error' ? _t('it_dc_token_bad') : _t('it_dc_connecting'));
|
|
1645
|
+
});
|
|
1646
|
+
$('#dcStopBtn').addEventListener('click', async () => {
|
|
1647
|
+
await beast.dcStop().catch(() => {});
|
|
1648
|
+
updateDcPane();
|
|
1649
|
+
toast('Kesildi');
|
|
1650
|
+
});
|
|
1651
|
+
await renderDcAllow();
|
|
1652
|
+
try {
|
|
1653
|
+
const ds = await beast.dcGetStatus();
|
|
1654
|
+
dcUI.status = ds.status || 'disconnected';
|
|
1655
|
+
dcUI.user = ds.user || null;
|
|
1656
|
+
updateDcPane();
|
|
1657
|
+
} catch {}
|
|
1600
1658
|
}
|
|
1601
1659
|
|
|
1602
1660
|
/* ---------------- Olay Merkezi (ayrı sekme) ---------------- */
|
|
@@ -2439,6 +2497,199 @@ async function renderTgAllow() {
|
|
|
2439
2497
|
}
|
|
2440
2498
|
}
|
|
2441
2499
|
|
|
2500
|
+
/* ---------------- Discord izin listesi + durum — TG ile aynı mantık ---------------- */
|
|
2501
|
+
|
|
2502
|
+
const dcUI = { status: 'disconnected', user: null };
|
|
2503
|
+
const DC_STATUS_TEXT = {
|
|
2504
|
+
disconnected: 'Bağlı değil',
|
|
2505
|
+
connecting: 'Bağlanıyor…',
|
|
2506
|
+
connected: 'Bağlı',
|
|
2507
|
+
error: 'Hata — tokenı kontrol et',
|
|
2508
|
+
};
|
|
2509
|
+
|
|
2510
|
+
function onDcEvent(ev) {
|
|
2511
|
+
if (ev.type !== 'status') return;
|
|
2512
|
+
dcUI.status = ev.status;
|
|
2513
|
+
if (ev.user) dcUI.user = ev.user;
|
|
2514
|
+
updateDcPane();
|
|
2515
|
+
}
|
|
2516
|
+
|
|
2517
|
+
function updateDcPane() {
|
|
2518
|
+
const pane = $('#tab-integrations');
|
|
2519
|
+
const dot = pane && pane.querySelector('#dcDot');
|
|
2520
|
+
if (!dot) return;
|
|
2521
|
+
dot.className = 'wa-dot' + (dcUI.status === 'connected' ? ' on' : dcUI.status === 'error' ? ' qr' : '');
|
|
2522
|
+
pane.querySelector('#dcStatText').textContent = DC_STATUS_TEXT[dcUI.status] || dcUI.status;
|
|
2523
|
+
const u = pane.querySelector('#dcUser');
|
|
2524
|
+
u.hidden = !(dcUI.status === 'connected' && dcUI.user);
|
|
2525
|
+
u.textContent = dcUI.user ? '🤖 ' + dcUI.user : '';
|
|
2526
|
+
}
|
|
2527
|
+
|
|
2528
|
+
async function renderDcAllow() {
|
|
2529
|
+
const wrap = $('#dcAllowChips');
|
|
2530
|
+
if (!wrap) return;
|
|
2531
|
+
const list = await beast.dcGetAllow();
|
|
2532
|
+
let botChoices = [];
|
|
2533
|
+
try { botChoices = (await beast.botsList()) || []; } catch {}
|
|
2534
|
+
const dcLabel = (e) => {
|
|
2535
|
+
if (e === '*') return '* herkes';
|
|
2536
|
+
if (typeof e === 'string') return e;
|
|
2537
|
+
const name = String((e && e.name) || '').trim();
|
|
2538
|
+
const id = String((e && e.id) || '');
|
|
2539
|
+
const bot = e && e.bot_id ? botChoices.find((bb) => bb.id === e.bot_id) : null;
|
|
2540
|
+
return (name ? name + ' ' : '') + id + (bot ? ' → ' + bot.name : '');
|
|
2541
|
+
};
|
|
2542
|
+
wrap.innerHTML = '';
|
|
2543
|
+
if (!list.length) wrap.innerHTML = '<span class="sub">— boş —</span>';
|
|
2544
|
+
list.forEach((entry, idx) => {
|
|
2545
|
+
const curPerm = (typeof entry === 'object' && entry.perm) || (entry && entry.lockdown ? 'chat' : 'all');
|
|
2546
|
+
const c = document.createElement('span');
|
|
2547
|
+
c.className = 'chip';
|
|
2548
|
+
c.style.margin = '0 6px 6px 0';
|
|
2549
|
+
const txt = document.createElement('span');
|
|
2550
|
+
txt.className = 'chip-txt';
|
|
2551
|
+
txt.textContent = dcLabel(entry);
|
|
2552
|
+
c.appendChild(txt);
|
|
2553
|
+
if (entry !== '*') {
|
|
2554
|
+
const selP = document.createElement('select');
|
|
2555
|
+
selP.className = 'perm-select';
|
|
2556
|
+
selP.title = _t('wa_perm_title');
|
|
2557
|
+
const PERMS = [
|
|
2558
|
+
['all', _t('wa_perm_all')],
|
|
2559
|
+
['web', 'web'],
|
|
2560
|
+
['read', _t('wa_perm_read')],
|
|
2561
|
+
['chat', _t('wa_perm_chat')],
|
|
2562
|
+
];
|
|
2563
|
+
for (const [v, lbl] of PERMS) {
|
|
2564
|
+
const o = document.createElement('option');
|
|
2565
|
+
o.value = v;
|
|
2566
|
+
o.textContent = lbl;
|
|
2567
|
+
if (curPerm === v) o.selected = true;
|
|
2568
|
+
selP.appendChild(o);
|
|
2569
|
+
}
|
|
2570
|
+
selP.addEventListener('change', async () => {
|
|
2571
|
+
const cur = await beast.dcGetAllow();
|
|
2572
|
+
const next = cur.map((e, i) => {
|
|
2573
|
+
if (i !== idx) return e;
|
|
2574
|
+
const base = typeof e === 'string' ? { id: e, name: '' } : { ...e };
|
|
2575
|
+
base.lockdown = selP.value === 'chat';
|
|
2576
|
+
base.perm = selP.value;
|
|
2577
|
+
return base;
|
|
2578
|
+
});
|
|
2579
|
+
await beast.dcSetAllow(next);
|
|
2580
|
+
renderDcAllow();
|
|
2581
|
+
toast(dcLabel(entry) + ': ' + selP.selectedOptions[0].textContent);
|
|
2582
|
+
});
|
|
2583
|
+
c.appendChild(selP);
|
|
2584
|
+
|
|
2585
|
+
if (botChoices.length) {
|
|
2586
|
+
const sel = document.createElement('select');
|
|
2587
|
+
sel.className = 'perm-select';
|
|
2588
|
+
sel.title = _t('bot_bind_title');
|
|
2589
|
+
const empty = document.createElement('option');
|
|
2590
|
+
empty.value = '';
|
|
2591
|
+
empty.textContent = _t('bot_sel_empty');
|
|
2592
|
+
sel.appendChild(empty);
|
|
2593
|
+
for (const bb of botChoices) {
|
|
2594
|
+
const o = document.createElement('option');
|
|
2595
|
+
o.value = bb.id;
|
|
2596
|
+
o.textContent = `${bb.icon} ${bb.name}`;
|
|
2597
|
+
if ((typeof entry === 'object' && entry.bot_id) === bb.id) o.selected = true;
|
|
2598
|
+
sel.appendChild(o);
|
|
2599
|
+
}
|
|
2600
|
+
sel.addEventListener('change', async () => {
|
|
2601
|
+
const cur = await beast.dcGetAllow();
|
|
2602
|
+
const next = cur.map((e, i) => {
|
|
2603
|
+
if (i !== idx) return e;
|
|
2604
|
+
const base = typeof e === 'string' ? { id: e, name: '' } : { ...e };
|
|
2605
|
+
base.bot_id = sel.value || undefined;
|
|
2606
|
+
return base;
|
|
2607
|
+
});
|
|
2608
|
+
await beast.dcSetAllow(next);
|
|
2609
|
+
renderDcAllow();
|
|
2610
|
+
const bt = botChoices.find((bb) => bb.id === sel.value);
|
|
2611
|
+
toast(dcLabel(entry) + ' → ' + (bt ? bt.name : '?'));
|
|
2612
|
+
});
|
|
2613
|
+
c.appendChild(sel);
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2616
|
+
const isOwnerEntry = typeof entry === 'object' && !!entry.owner;
|
|
2617
|
+
const ownerBtn = document.createElement('span');
|
|
2618
|
+
ownerBtn.className = 'lk';
|
|
2619
|
+
ownerBtn.style.cssText = 'font-size:11px;padding:1px 6px;' + (isOwnerEntry ? 'color:#c9a227;font-weight:800' : '');
|
|
2620
|
+
ownerBtn.title = isOwnerEntry ? _t('wa_owner_title_on') : _t('wa_owner_title_off');
|
|
2621
|
+
ownerBtn.textContent = isOwnerEntry ? _t('wa_owner_on') : _t('wa_owner_off');
|
|
2622
|
+
ownerBtn.addEventListener('click', async () => {
|
|
2623
|
+
if (isOwnerEntry) return;
|
|
2624
|
+
const cur = await beast.dcGetAllow();
|
|
2625
|
+
const next = cur.map((e, i) => {
|
|
2626
|
+
if (typeof e === 'object' && e && e.owner) return { ...e, owner: false };
|
|
2627
|
+
if (i === idx) {
|
|
2628
|
+
const obj = typeof e === 'string' ? { id: e, name: '' } : { ...e };
|
|
2629
|
+
obj.owner = true;
|
|
2630
|
+
return obj;
|
|
2631
|
+
}
|
|
2632
|
+
return e;
|
|
2633
|
+
});
|
|
2634
|
+
const tgt = next[idx];
|
|
2635
|
+
await beast.dcSetAllow(next);
|
|
2636
|
+
renderDcAllow();
|
|
2637
|
+
toast('Sahip: ' + dcLabel(tgt));
|
|
2638
|
+
});
|
|
2639
|
+
c.appendChild(ownerBtn);
|
|
2640
|
+
}
|
|
2641
|
+
const x = document.createElement('span');
|
|
2642
|
+
x.className = 'x';
|
|
2643
|
+
x.textContent = '×';
|
|
2644
|
+
x.addEventListener('click', async () => {
|
|
2645
|
+
const next = (await beast.dcGetAllow()).filter((_v, i) => i !== idx);
|
|
2646
|
+
await beast.dcSetAllow(next);
|
|
2647
|
+
renderDcAllow();
|
|
2648
|
+
toast('Kaldırıldı: ' + dcLabel(entry));
|
|
2649
|
+
});
|
|
2650
|
+
c.appendChild(x);
|
|
2651
|
+
wrap.appendChild(c);
|
|
2652
|
+
});
|
|
2653
|
+
|
|
2654
|
+
const inp = $('#dcAllowIdInp');
|
|
2655
|
+
const nameInp = $('#dcAllowNameInp');
|
|
2656
|
+
const add = $('#dcAllowAdd');
|
|
2657
|
+
const botSel = $('#dcAllowBotSel');
|
|
2658
|
+
if (botSel) {
|
|
2659
|
+
const prev = botSel.value;
|
|
2660
|
+
botSel.innerHTML =
|
|
2661
|
+
`<option value="">${_t('bot_sel_empty')}</option>` +
|
|
2662
|
+
botChoices
|
|
2663
|
+
.map((bb) => `<option value="${bb.id}">${bb.icon} ${escapeHtml(bb.name)}${bb.admin ? ' (admin)' : ''}</option>`)
|
|
2664
|
+
.join('');
|
|
2665
|
+
if (prev) botSel.value = prev;
|
|
2666
|
+
}
|
|
2667
|
+
if (!add.dataset.bound) {
|
|
2668
|
+
add.dataset.bound = '1';
|
|
2669
|
+
const addEntry = async () => {
|
|
2670
|
+
let v = inp.value.trim();
|
|
2671
|
+
if (!v) return;
|
|
2672
|
+
const name = nameInp.value.trim().slice(0, 40);
|
|
2673
|
+
if (!name) { toast('İsim zorunlu — kimin yazdığını bilmek için'); nameInp.focus(); return; }
|
|
2674
|
+
v = v.startsWith('@') ? '@' + v.slice(1).replace(/[^\w]/g, '') : v.replace(/[^\d]/g, '');
|
|
2675
|
+
if (!v) { toast(_t('it_dc_id_ph')); return; }
|
|
2676
|
+
const botId = botSel && botSel.value ? { bot_id: botSel.value } : {};
|
|
2677
|
+
const next = [...(await beast.dcGetAllow()), { id: v, name, ...botId }];
|
|
2678
|
+
if (!next.some((e) => e && typeof e === 'object' && e.owner)) {
|
|
2679
|
+
if (next.length && typeof next[0] === 'object') next[0].owner = true;
|
|
2680
|
+
}
|
|
2681
|
+
await beast.dcSetAllow(next);
|
|
2682
|
+
inp.value = '';
|
|
2683
|
+
nameInp.value = '';
|
|
2684
|
+
renderDcAllow();
|
|
2685
|
+
toast('Eklendi: ' + name);
|
|
2686
|
+
};
|
|
2687
|
+
add.addEventListener('click', addEntry);
|
|
2688
|
+
inp.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); addEntry(); } });
|
|
2689
|
+
nameInp.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); addEntry(); } });
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
|
|
2442
2693
|
/* ---------------- BOT SİSTEMİ (BÖLÜM 2-3-4) ----------------
|
|
2443
2694
|
Sol panel altında bot kartları; her bot için sohbet geçmişi + sekmeli yönetim.
|
|
2444
2695
|
İlk bot hep Beast (admin, silinemez). Max 5 bot. */
|
|
@@ -2474,6 +2725,8 @@ async function refreshBots() {
|
|
|
2474
2725
|
/* BOT PICKER SENKRONU: liste her yenilendiğinde üstteki rozet de aktif bota
|
|
2475
2726
|
ayarlanır — startup yarışında (liste boşken çizilen) eski rozet düzelir */
|
|
2476
2727
|
updateBotChip();
|
|
2728
|
+
/* model picker da aktif botun kendi modelini göstersin */
|
|
2729
|
+
if (state) applyState();
|
|
2477
2730
|
if (!$('#botOverlay').hidden) renderBotPage();
|
|
2478
2731
|
}
|
|
2479
2732
|
|
|
@@ -2531,12 +2784,15 @@ async function switchBot(id) {
|
|
|
2531
2784
|
}
|
|
2532
2785
|
} catch {}
|
|
2533
2786
|
try { localStorage.setItem('beast.activeBot', id); } catch {}
|
|
2534
|
-
/* o botun en son oturumuna geç; hiç yoksa o bot için yeni sohbet
|
|
2787
|
+
/* o botun en son oturumuna geç; hiç yoksa o bot için yeni sohbet aç.
|
|
2788
|
+
(bot kaydı olmayan eski oturumlar da sayılır — ilk mesajda bağlanır) */
|
|
2535
2789
|
try {
|
|
2536
|
-
const list = (await beast.listSessions()).filter((s) =>
|
|
2790
|
+
const list = (await beast.listSessions()).filter((s) => !s.botId || s.botId === id);
|
|
2537
2791
|
if (list.length) await openSession(list[0].id);
|
|
2538
2792
|
else { const v = await beast.createSession(); await openSession(v.id); }
|
|
2539
2793
|
} catch {}
|
|
2794
|
+
/* picker, aktif botun kendi modelini göstersin */
|
|
2795
|
+
applyState();
|
|
2540
2796
|
toast((b.icon || '🤖') + ' ' + b.name + (b.admin ? '' : ' — ' + _t('bot_switched')));
|
|
2541
2797
|
}
|
|
2542
2798
|
|
|
@@ -2638,7 +2894,7 @@ async function renderBotNotes(pane, b) {
|
|
|
2638
2894
|
pane.innerHTML = `<h2>${_t('notes_h2')}</h2><div class="sub">${_t('notes_sub')}</div>`;
|
|
2639
2895
|
let all = [];
|
|
2640
2896
|
try { all = await beast.listNotes(); } catch {}
|
|
2641
|
-
const list = (b.admin ? all : all.filter((n) =>
|
|
2897
|
+
const list = (b.admin ? all : all.filter((n) => !n.botId || n.botId === b.id))
|
|
2642
2898
|
.slice()
|
|
2643
2899
|
.sort((x, y) => String(y.updatedAt).localeCompare(String(x.updatedAt)));
|
|
2644
2900
|
if (!list.length) {
|
|
@@ -2699,7 +2955,8 @@ async function renderBotChats(b) {
|
|
|
2699
2955
|
const sessions = [];
|
|
2700
2956
|
try {
|
|
2701
2957
|
for (const s of await beast.listSessions()) {
|
|
2702
|
-
|
|
2958
|
+
/* bot kaydı olmayan (eski) oturumlar da gösterilir — ilk mesajda bu bota bağlanır */
|
|
2959
|
+
if (!s.botId || s.botId === b.id) sessions.push(s);
|
|
2703
2960
|
}
|
|
2704
2961
|
} catch {}
|
|
2705
2962
|
sessions.sort((x, y) => String(y.updatedAt).localeCompare(String(x.updatedAt)));
|
|
@@ -2767,6 +3024,15 @@ function renderBotSettings(pane, b) {
|
|
|
2767
3024
|
const numRows = (b.numbers || [])
|
|
2768
3025
|
.map((n) => `<div class="bot-num-row" data-num="${n.num}"><span class="n">+${escapeHtml(n.num)}${n.name ? ' · ' + escapeHtml(n.name) : ''}</span><span class="x" title="${_t('bot_num_del')}">×</span></div>`)
|
|
2769
3026
|
.join('');
|
|
3027
|
+
/* bot bazlı model seçenekleri: ana picker'daki listeyle aynı (state.models) */
|
|
3028
|
+
const modelOpts = ['<option value="">' + escapeHtml(_t('bot_model_global')) + '</option>']
|
|
3029
|
+
.concat(
|
|
3030
|
+
(state.models || []).map((m) => {
|
|
3031
|
+
const s = m.sel || (m.providerId || '') + '::' + (m.model || '');
|
|
3032
|
+
return `<option value="${escapeHtml(s)}" ${b.model === s ? 'selected' : ''}>${escapeHtml(m.providerName || m.providerId || '')} · ${escapeHtml(m.model || '')}</option>`;
|
|
3033
|
+
})
|
|
3034
|
+
)
|
|
3035
|
+
.join('');
|
|
2770
3036
|
/* Numara ekleme KALDIRILDI — girişler yalnız Ayarlar → Entegrasyonlar (WhatsApp
|
|
2771
3037
|
izin listesi) üzerinden yapılır. Bu bölüm salt-okunur bağlı-numara listesidir. */
|
|
2772
3038
|
f.innerHTML = `
|
|
@@ -2789,7 +3055,10 @@ function renderBotSettings(pane, b) {
|
|
|
2789
3055
|
<div class="bot-checks" id="bPerm">${permChecks}</div>
|
|
2790
3056
|
<div class="sub" style="margin-top:4px">${_t('bot_perm_note')}</div>
|
|
2791
3057
|
<label class="mem-label">${_t('bot_skills')}</label>
|
|
2792
|
-
<div class="bot-checks" id="bSkills">${skillChecks}</div
|
|
3058
|
+
<div class="bot-checks" id="bSkills">${skillChecks}</div>
|
|
3059
|
+
<label class="mem-label">${_t('bot_model')}</label>
|
|
3060
|
+
<select id="bModel" class="inp">${modelOpts}</select>
|
|
3061
|
+
<div class="sub" style="margin-top:4px">${_t('bot_model_note')}</div>`}
|
|
2793
3062
|
<label class="mem-label">${_t('bot_browser')}</label>
|
|
2794
3063
|
<div class="bot-checks" style="margin-bottom:6px">
|
|
2795
3064
|
<label><input type="checkbox" id="bExtBrowser" ${b.extBrowser ? 'checked' : ''}/> ${_t('bot_ext_browser')}</label>
|
|
@@ -2864,6 +3133,8 @@ function renderBotSettings(pane, b) {
|
|
|
2864
3133
|
const sk = {};
|
|
2865
3134
|
f.querySelectorAll('#bSkills input').forEach((c) => { sk[c.dataset.skill] = c.checked; });
|
|
2866
3135
|
patch.skills = sk;
|
|
3136
|
+
const bm = $('#bModel');
|
|
3137
|
+
if (bm) patch.model = bm.value;
|
|
2867
3138
|
}
|
|
2868
3139
|
const r = await beast.botsUpdate(b.id, patch);
|
|
2869
3140
|
if (r.ok) { toast(_t('bot_saved')); await refreshBots(); renderBotPage(); }
|
|
@@ -2921,7 +3192,7 @@ async function renderBotWatcher(pane, b) {
|
|
|
2921
3192
|
try { items = (await beast.watchersList()) || []; } catch {}
|
|
2922
3193
|
let botSessions = new Set();
|
|
2923
3194
|
try {
|
|
2924
|
-
for (const s of await beast.listSessions()) if (
|
|
3195
|
+
for (const s of await beast.listSessions()) if (!s.botId || s.botId === b.id) botSessions.add(s.id);
|
|
2925
3196
|
} catch {}
|
|
2926
3197
|
const mine = items.filter((w) => botSessions.has(w.sessionId));
|
|
2927
3198
|
if (!mine.length) {
|
|
@@ -3087,6 +3358,39 @@ function fmtAgo(iso) {
|
|
|
3087
3358
|
return s >= 60 ? Math.floor(s / 60) + 'dk' : s + 'sn';
|
|
3088
3359
|
}
|
|
3089
3360
|
|
|
3361
|
+
/* canlı süre sayacı: 0:43 · 2:05 · 1:02:15 biçimi (koşan ajan kartlarında) */
|
|
3362
|
+
function fmtElapsed(ms) {
|
|
3363
|
+
const s = Math.max(0, Math.floor(ms / 1000));
|
|
3364
|
+
const h = Math.floor(s / 3600);
|
|
3365
|
+
const m = Math.floor((s % 3600) / 60);
|
|
3366
|
+
const sec = s % 60;
|
|
3367
|
+
const p = (n) => String(n).padStart(2, '0');
|
|
3368
|
+
return h > 0 ? `${h}:${p(m)}:${p(sec)}` : `${m}:${p(sec)}`;
|
|
3369
|
+
}
|
|
3370
|
+
|
|
3371
|
+
/* koşan ajanların ⏱ sayaçları — saniyede bir yalnızca sayaç span'leri güncellenir
|
|
3372
|
+
(tüm rail yeniden çizilmez). Koşan kart kalmayınca interval kapanır. */
|
|
3373
|
+
let agentTimerInt = null;
|
|
3374
|
+
function ensureAgentTimers() {
|
|
3375
|
+
const any = !!document.querySelector('.agent-timer[data-start]');
|
|
3376
|
+
if (any && !agentTimerInt) {
|
|
3377
|
+
agentTimerInt = setInterval(() => {
|
|
3378
|
+
document.querySelectorAll('.agent-timer[data-start]').forEach((el) => {
|
|
3379
|
+
const t0 = Date.parse(el.dataset.start || '');
|
|
3380
|
+
if (t0) el.textContent = fmtElapsed(Date.now() - t0);
|
|
3381
|
+
});
|
|
3382
|
+
}, 1000);
|
|
3383
|
+
} else if (!any && agentTimerInt) {
|
|
3384
|
+
clearInterval(agentTimerInt);
|
|
3385
|
+
agentTimerInt = null;
|
|
3386
|
+
}
|
|
3387
|
+
}
|
|
3388
|
+
function agentTimerHtml(startedAt) {
|
|
3389
|
+
const t0 = Date.parse(startedAt || '');
|
|
3390
|
+
const val = t0 ? fmtElapsed(Date.now() - t0) : '0:00';
|
|
3391
|
+
return `<span class="agent-timer" data-start="${escapeHtml(startedAt || '')}">⏱ ${val}</span>`;
|
|
3392
|
+
}
|
|
3393
|
+
|
|
3090
3394
|
/* sağ panel — paralel ajanların arka plan işleri */
|
|
3091
3395
|
function renderAgentRail() {
|
|
3092
3396
|
const list = els.railList;
|
|
@@ -3117,7 +3421,7 @@ function renderAgentRail() {
|
|
|
3117
3421
|
`<span class="ag-dot"></span>` +
|
|
3118
3422
|
`<span class="sess-title">${escapeHtml(j.title)}</span>` +
|
|
3119
3423
|
(j.code ? `<span class="sess-code" title="Oturum kodu">${escapeHtml(j.code)}</span>` : ``) +
|
|
3120
|
-
`<span class="rj-time">${j.status === 'running' ? when + ' · ' + _t('ag_working') : (agStText(j.status) === _t('ag_st_done') ? '\u2713' : (agStText(j.status) || j.status))}</span>` +
|
|
3424
|
+
`<span class="rj-time">${j.status === 'running' ? when + ' · ' + agentTimerHtml(j.startedAt) + ' · ' + _t('ag_working') : (agStText(j.status) === _t('ag_st_done') ? '\u2713' : (agStText(j.status) || j.status))}</span>` +
|
|
3121
3425
|
(j.status === 'running'
|
|
3122
3426
|
? `<button class="rj-cancel" title="${_t('ag_cancel')}">×</button>`
|
|
3123
3427
|
: `<button class="rj-cancel" title="${_t('ag_delete')}"><svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M8 6V4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v2"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M10 11v6M14 11v6"/></svg></button>`) +
|
|
@@ -3162,6 +3466,7 @@ function renderAgentRail() {
|
|
|
3162
3466
|
/* açık sohbet dökümünü re-render sonrası GERİ GETİR (kapanma bug'ının fix'i) */
|
|
3163
3467
|
if (open && agentState.chatOpen.has(j.id)) maybeLoadRailChat(j.id);
|
|
3164
3468
|
}
|
|
3469
|
+
ensureAgentTimers();
|
|
3165
3470
|
}
|
|
3166
3471
|
|
|
3167
3472
|
/* re-render storm'da IPC fırtınası olmasın: döküm 2 sn'de bir tazelenir */
|
|
@@ -3247,7 +3552,7 @@ function renderAgentsPane() {
|
|
|
3247
3552
|
`<span class="ag-dot"></span>` +
|
|
3248
3553
|
`<span class="ag-title">${escapeHtml(j.title)}</span>` +
|
|
3249
3554
|
`<span class="ag-st">${agStText(j.status) || j.status}</span>` +
|
|
3250
|
-
`<span class="ag-time">${when}${j.endedAt ? ` · ${fmtAgo(j.endedAt)}` : ''}</span>` +
|
|
3555
|
+
`<span class="ag-time">${when}${j.status === 'running' ? ' · ' + agentTimerHtml(j.startedAt) : j.endedAt ? ` · ${fmtAgo(j.endedAt)}` : ''}</span>` +
|
|
3251
3556
|
(j.status === 'running'
|
|
3252
3557
|
? `<button class="ag-btn cancel" title="${_t('ag_cancel')}">${_t('ag_cancel')}</button>`
|
|
3253
3558
|
: ``) +
|
|
@@ -3285,16 +3590,8 @@ function renderAgentsPane() {
|
|
|
3285
3590
|
list.appendChild(card);
|
|
3286
3591
|
}
|
|
3287
3592
|
|
|
3288
|
-
/* canlı süre sayacı */
|
|
3289
|
-
|
|
3290
|
-
agentState.tick = setInterval(() => {
|
|
3291
|
-
if (setTab !== 'agents') { clearInterval(agentState.tick); return; }
|
|
3292
|
-
for (const el of document.querySelectorAll('.agent-card.st-running .ag-time')) {
|
|
3293
|
-
/* sadece saati yenile */
|
|
3294
|
-
const t = el.textContent.split(' ')[0];
|
|
3295
|
-
el.textContent = `${t} · ${_t('ag_working')}…`;
|
|
3296
|
-
}
|
|
3297
|
-
}, 5000);
|
|
3593
|
+
/* canlı süre sayacı — saniyede bir yalnız ⏱ sayaç span'leri güncellenir */
|
|
3594
|
+
ensureAgentTimers();
|
|
3298
3595
|
}
|
|
3299
3596
|
|
|
3300
3597
|
/* ---------------- events from engine ---------------- */
|
|
@@ -3738,15 +4035,26 @@ async function openCronModal() {
|
|
|
3738
4035
|
|
|
3739
4036
|
/* ---------------- state / controls ---------------- */
|
|
3740
4037
|
|
|
4038
|
+
/* AKTİF BOTUN etkili model seçimi: müşteri botun kendi modeli varsa o,
|
|
4039
|
+
yoksa (ve admin bot daysa) global picker seçimi. */
|
|
4040
|
+
function effectiveModelSel() {
|
|
4041
|
+
const b = botsCache.find((x) => x.id === activeBotId);
|
|
4042
|
+
return b && !b.admin && b.model ? b.model : (state.activeModel && state.activeModel.sel) || '';
|
|
4043
|
+
}
|
|
4044
|
+
|
|
3741
4045
|
function applyState() {
|
|
3742
4046
|
if (!state) return;
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
4047
|
+
const botSel = activeBotId && botsCache.length ? effectiveModelSel() : (state.activeModel && state.activeModel.sel);
|
|
4048
|
+
const bm = botSel ? (state.models || []).find((x) => x.sel === botSel) : null;
|
|
4049
|
+
els.modelBtnLabel.textContent = bm
|
|
4050
|
+
? `${bm.providerName} · ${bm.model}`
|
|
4051
|
+
: state.activeModel
|
|
4052
|
+
? `${state.activeModel.providerName} · ${state.activeModel.model}`
|
|
4053
|
+
: 'Model seçilmedi';
|
|
3746
4054
|
els.thinkBtnLabel.innerHTML =
|
|
3747
|
-
'<svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.5 2A2.5 2.5 0 0 0 7 4.5v.55A3.5 3.5 0 0 0 4.5 8.5c0 .74.23 1.43.62 2A3.5 3.5 0 0 0 4 13.5 3.5 3.5 0 0 0 7 16.95v.55A2.5 2.5 0 0 0 9.5 20a2.5 2.5 0 0 0 2.5-2.5v-13A2.5 2.5 0 0 0 9.5 2z"/><path d="M14.5 2A2.5 2.5 0 0 1 17 4.5v.55a3.5 3.5 0 0 1 2.5 3.45c0 .74-.23 1.43-.62 2a3.5 3.5 0 0 1
|
|
4055
|
+
'<svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.5 2A2.5 2.5 0 0 0 7 4.5v.55A3.5 3.5 0 0 0 4.5 8.5c0 .74.23 1.43.62 2A3.5 3.5 0 0 0 4 13.5 3.5 3.5 0 0 0 7 16.95v.55A2.5 2.5 0 0 0 9.5 20a2.5 2.5 0 0 0 2.5-2.5v-13A2.5 2.5 0 0 0 9.5 2z"/><path d="M14.5 2A2.5 2.5 0 0 1 17 4.5v.55a3.5 3.5 0 0 1 2.5 3.45c0 .74-.23 1.43-.62 2a3.5 3.5 0 0 1-1.12 3 3.5 3.5 0 0 1-3 3.45v.55A2.5 2.5 0 0 1 14.5 20 2.5 2.5 0 0 1 12 17.5v-13A2.5 2.5 0 0 1 14.5 2z"/></svg>';
|
|
3748
4056
|
renderModelMenu();
|
|
3749
|
-
}
|
|
4057
|
+
}
|
|
3750
4058
|
|
|
3751
4059
|
/* düşünme (reasoning) seviyesi picker — gerçek API değerleri */
|
|
3752
4060
|
const THINK_UI_LEVELS = [
|
|
@@ -3803,7 +4111,7 @@ function renderModelMenu() {
|
|
|
3803
4111
|
els.modelList.innerHTML = '';
|
|
3804
4112
|
for (const m of list) {
|
|
3805
4113
|
const item = document.createElement('div');
|
|
3806
|
-
item.className = 'dd-item' + (
|
|
4114
|
+
item.className = 'dd-item' + (effectiveModelSel() && effectiveModelSel() === m.sel ? ' active' : '');
|
|
3807
4115
|
item.innerHTML =
|
|
3808
4116
|
`<span class="dn">${escapeHtml(m.providerName)}</span>` +
|
|
3809
4117
|
`<span class="dm">${escapeHtml(m.model)}</span>` +
|
|
@@ -3811,6 +4119,7 @@ function renderModelMenu() {
|
|
|
3811
4119
|
item.addEventListener('click', async () => {
|
|
3812
4120
|
state = await beast.setModel(m.sel);
|
|
3813
4121
|
closeModelMenu();
|
|
4122
|
+
await refreshBots().catch(() => {}); // bot modeli değiştiyse botsCache tazelensin
|
|
3814
4123
|
applyState();
|
|
3815
4124
|
});
|
|
3816
4125
|
els.modelList.appendChild(item);
|
|
@@ -4262,6 +4571,7 @@ async function init() {
|
|
|
4262
4571
|
|
|
4263
4572
|
beast.onWaEvent(onWaEvent);
|
|
4264
4573
|
beast.onTgEvent(onTgEvent);
|
|
4574
|
+
beast.onDcEvent(onDcEvent);
|
|
4265
4575
|
|
|
4266
4576
|
els.gearBtn.addEventListener('click', openSettings);
|
|
4267
4577
|
els.setClose.addEventListener('click', closeSettings);
|
|
@@ -4464,7 +4774,7 @@ async function init() {
|
|
|
4464
4774
|
if ($('#botOverviewBack')) $('#botOverviewBack').addEventListener('click', () => openBotPage(null));
|
|
4465
4775
|
if ($('#botChip')) $('#botChip').addEventListener('click', () => openBotPage(activeBotId));
|
|
4466
4776
|
/* kalıcı aktif bot: restart sonrası aynı botun UI'ı açılır */
|
|
4467
|
-
beast.botsActiveGet().then((r) => { activeBotId = (r && r.id) || 'beast'; updateBotChip(); renderBotCards(); }).catch(() => {});
|
|
4777
|
+
beast.botsActiveGet().then((r) => { activeBotId = (r && r.id) || 'beast'; updateBotChip(); renderBotCards(); applyState(); }).catch(() => {});
|
|
4468
4778
|
document.querySelectorAll('.btab').forEach((b) =>
|
|
4469
4779
|
b.addEventListener('click', () => {
|
|
4470
4780
|
document.querySelectorAll('.btab').forEach((x) => x.classList.remove('active'));
|
package/tests/bg-jobs.test.js
CHANGED
|
@@ -121,18 +121,14 @@ function fakeJob(over) {
|
|
|
121
121
|
};
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
test('superviseReason: takılan stuck,
|
|
124
|
+
test('superviseReason: takılan stuck, uzun koşan iş CEO kontrolü (long), yeni uyarılan null', () => {
|
|
125
125
|
assert.strictEqual(Engine.superviseReason(fakeJob(), Date.now()), 'stuck');
|
|
126
126
|
|
|
127
|
+
// süre sınırı KALDIRILDI: aktivite süren uzun iş artık kesilmez → CEO ara kontrolü (long)
|
|
127
128
|
assert.strictEqual(
|
|
128
129
|
Engine.superviseReason(fakeJob({ lastActivityAt: new Date(Date.now() - 30 * 1000).toISOString() }), Date.now()),
|
|
129
|
-
'wrapup'
|
|
130
|
-
); // aktivite sürüyor ama 10 dk koştu — önce zarif bitirme uyarısı
|
|
131
|
-
|
|
132
|
-
assert.strictEqual(
|
|
133
|
-
Engine.superviseReason(fakeJob({ wrapAt: new Date().toISOString(), lastActivityAt: new Date(Date.now() - 30 * 1000).toISOString() }), Date.now()),
|
|
134
130
|
'long'
|
|
135
|
-
);
|
|
131
|
+
);
|
|
136
132
|
|
|
137
133
|
assert.strictEqual(
|
|
138
134
|
Engine.superviseReason(fakeJob({ startedAt: new Date(Date.now() - 1 * MIN).toISOString() }), Date.now()),
|
|
@@ -182,12 +178,11 @@ test('_supervise önce ajana ÖZ-KURTARMA verir, haklar bitince CEO\'ya uyarır'
|
|
|
182
178
|
eng._supervise();
|
|
183
179
|
assert.strictEqual(job.checks, 1);
|
|
184
180
|
|
|
185
|
-
/* 2. aşama: öz-kurtarma hakları bitmiş + iş hâlâ koşuyor (
|
|
186
|
-
|
|
181
|
+
/* 2. aşama: öz-kurtarma hakları bitmiş + iş hâlâ koşuyor (aktivite sürüyor,
|
|
182
|
+
süre sınırı yok) → CEO uyarısı ('long') */
|
|
187
183
|
job.status = 'running';
|
|
188
184
|
job.endedAt = null;
|
|
189
185
|
job.fixes = 2;
|
|
190
|
-
job.wrapAt = new Date().toISOString();
|
|
191
186
|
job.lastActivityAt = new Date(Date.now() - 30 * 1000).toISOString();
|
|
192
187
|
job.lastNudgeAt = new Date(Date.now() - 5 * MIN).toISOString();
|
|
193
188
|
eng._supervise();
|