beast-agent 2.16.0 → 2.18.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 +1 -1
- package/src/agent/engine.js +152 -20
- package/src/agent/supermemory.js +132 -0
- package/src/agent/watchers.js +93 -1
- package/src/cron.js +46 -7
- package/src/main.js +174 -8
- package/src/preload.js +7 -0
- package/src/renderer/i18n.js +16 -0
- package/src/renderer/index.html +18 -1
- package/src/renderer/renderer.js +110 -2
- package/src/renderer/style.css +48 -3
package/package.json
CHANGED
package/src/agent/engine.js
CHANGED
|
@@ -17,6 +17,7 @@ const research = require('./research');
|
|
|
17
17
|
const agentdefs = require('./agentdefs');
|
|
18
18
|
const memory = require('./memory');
|
|
19
19
|
const mem0 = require('./mem0');
|
|
20
|
+
const supermemory = require('./supermemory');
|
|
20
21
|
const nightref = require('./nightref');
|
|
21
22
|
const skills = require('./skills');
|
|
22
23
|
const mcp = require('./mcp');
|
|
@@ -275,6 +276,14 @@ class Engine {
|
|
|
275
276
|
/* mem0-native hafıza katmanı: semantik arama + LLM konsolidasyonu (bot başına izole store).
|
|
276
277
|
Ayarlardan kapatılabilir (mem0:false) — kapalıysa klasik keyword hafızası çalışır. */
|
|
277
278
|
this.mem0Enabled = opts.mem0 !== false;
|
|
279
|
+
/* Supermemory (lokal) bellek katmanı — DEFAULT AÇIK: sunucu ayaktaysa
|
|
280
|
+
(npx supermemory local → localhost:6767) hibrit arama + kullanıcı profili
|
|
281
|
+
öncelikli gelir; ayakta değilse klasik hafıza sorunsuz devralır.
|
|
282
|
+
Sağlık önbelleği: probe 60 sn'de bir tazelenir (spam probe yok). */
|
|
283
|
+
this.supermemory = opts.supermemory || null; // { enabled, baseUrl, apiKey, containerTag }
|
|
284
|
+
this._smUp = null; // null = henüz bilinmiyor
|
|
285
|
+
this._smCheckedAt = 0;
|
|
286
|
+
this._smWarned = false; // erişilemez iletisi bir kez basılır
|
|
278
287
|
/* ANA KOD KİLİDİ: agent kendi kaynak koduna dokunamaz (yazma/silme yok, okuma serbest) */
|
|
279
288
|
this.protectedDirs = (opts.protectedDirs || [])
|
|
280
289
|
.map((d) => String(d).replace(/[\\/]+$/, '').toLowerCase().replace(/\//g, '\\'))
|
|
@@ -1588,6 +1597,74 @@ class Engine {
|
|
|
1588
1597
|
}
|
|
1589
1598
|
}
|
|
1590
1599
|
|
|
1600
|
+
/* ---------- Supermemory (lokal) bellek köprüsü ---------- */
|
|
1601
|
+
|
|
1602
|
+
_smConfig() {
|
|
1603
|
+
const sm = this.supermemory;
|
|
1604
|
+
if (!sm || sm.enabled === false || !sm.baseUrl) return null;
|
|
1605
|
+
return sm;
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
/* Ayaktayken true — 60 sn cooldown'lu probe (aynı tura 10 kez probe vurmaz) */
|
|
1609
|
+
async _smReady() {
|
|
1610
|
+
const sm = this._smConfig();
|
|
1611
|
+
if (!sm) return false;
|
|
1612
|
+
const now = Date.now();
|
|
1613
|
+
if (this._smUp === true && now - this._smCheckedAt < supermemory.HEALTH_COOLDOWN_MS) return true;
|
|
1614
|
+
if (this._smUp === false && now - this._smCheckedAt < supermemory.HEALTH_COOLDOWN_MS) return false;
|
|
1615
|
+
this._smUp = await supermemory.probe(sm.baseUrl, sm.apiKey);
|
|
1616
|
+
this._smCheckedAt = now;
|
|
1617
|
+
if (!this._smUp && !this._smWarned) {
|
|
1618
|
+
this._smWarned = true;
|
|
1619
|
+
emitSafe(this, null, {
|
|
1620
|
+
type: 'status',
|
|
1621
|
+
status: '🧠 supermemory erişilemedi — klasik hafıza kullanılıyor (açmak için: npx supermemory local)',
|
|
1622
|
+
});
|
|
1623
|
+
}
|
|
1624
|
+
return this._smUp;
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
/* Container tag: bot oturumları kendi izole havuzuna yazar (mem0 deseni) */
|
|
1628
|
+
_smContainer(sessionId) {
|
|
1629
|
+
const sm = this._smConfig();
|
|
1630
|
+
const bctx = this._sessionBotCtx(sessionId ? this.cache.get(String(sessionId)) : null);
|
|
1631
|
+
return (bctx && bctx.id ? 'bot:' + bctx.id : '') || (sm && sm.containerTag) || 'beast';
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
/* YAZ: yerel hafıza HER ZAMEN yazılır (risksiz yedek); supermemory ASENKRON
|
|
1635
|
+
push edilir — turu bloklamaz, hata yutulur (mem0 deseninin aynısı) */
|
|
1636
|
+
_smPush(sessionId, text) {
|
|
1637
|
+
const sm = this._smConfig();
|
|
1638
|
+
if (!sm) return;
|
|
1639
|
+
supermemory
|
|
1640
|
+
.add(sm.baseUrl, sm.apiKey, { content: text, containerTag: this._smContainer(sessionId), metadata: { type: 'conversation' } })
|
|
1641
|
+
.catch(() => {});
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
/* ARA: supermemory hibrit arama + profil → yerel sonuçlarla BİRLEŞTİR.
|
|
1645
|
+
Sunucu yoksa/hata yerse eski akış aynen çalışır (fallback). */
|
|
1646
|
+
async _smSearch(sessionId, q, limit) {
|
|
1647
|
+
const sm = this._smConfig();
|
|
1648
|
+
if (!sm || !(await this._smReady())) return null;
|
|
1649
|
+
const container = this._smContainer(sessionId);
|
|
1650
|
+
try {
|
|
1651
|
+
const [hits, prof] = await Promise.all([
|
|
1652
|
+
supermemory.search(sm.baseUrl, sm.apiKey, { q, containerTag: container, limit, mode: 'hybrid' }),
|
|
1653
|
+
supermemory.profile(sm.baseUrl, sm.apiKey, { containerTag: container }).catch(() => null),
|
|
1654
|
+
]);
|
|
1655
|
+
const out = {
|
|
1656
|
+
engine: 'supermemory',
|
|
1657
|
+
results: hits.map((h) => ({ text: h.text, score: h.similarity, source: 'supermemory' })),
|
|
1658
|
+
};
|
|
1659
|
+
if (prof && (prof.static.length || prof.dynamic.length)) out.profile = prof;
|
|
1660
|
+
return out;
|
|
1661
|
+
} catch {
|
|
1662
|
+
this._smUp = false;
|
|
1663
|
+
this._smCheckedAt = Date.now();
|
|
1664
|
+
return null;
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1591
1668
|
/* opencode request.ts prepare — system birleşimi (birebir sıra) */
|
|
1592
1669
|
buildOcSystem(session, agentInfo) {
|
|
1593
1670
|
const sid = String(session && session.id);
|
|
@@ -1753,6 +1830,11 @@ class Engine {
|
|
|
1753
1830
|
: 'ROL: GÖZLEMCİ ANALİST 🔍 — tur tur piyasa tararsın ama OTOMATİK İŞLEM KAPALI: mt5_trade/mt5_pending KULLANMA. Fırsatları net öneri olarak yaz (sembol, yön, giriş, SL, TP, sebep); sahibi onaylarsa açılır.'
|
|
1754
1831
|
: 'ROL: TRADING CO-PİLOT 💬 — sahibinle sohbet ediyorsun. Veri isterse mt5_* araçlarıyla çek, analiz et; işlem istenirse limitler içinde uygula, istenmezse sadece öneri ver.';
|
|
1755
1832
|
const proj = this._projectInstructions(session);
|
|
1833
|
+
/* Skills kataloğu: finance oturumlarında da skill'ler çağrılabilir
|
|
1834
|
+
(skill aracı _chatTurn'de finance oturumlarına enjekte edilir). */
|
|
1835
|
+
const skList = (() => {
|
|
1836
|
+
try { return skills.scan(); } catch { return []; }
|
|
1837
|
+
})();
|
|
1756
1838
|
return (
|
|
1757
1839
|
'Sen BEAST FİNANS\u2019sın — bilgisayardaki MetaTrader 5 (MT5) terminaline köprüyle bağlı trading ajanı.\n' +
|
|
1758
1840
|
'ORTAM: Beast Finance uygulamasındasın — solda sohbet geçmişi + izleyiciler, ortada bu sohbet, SAĞ panelde MT5 işlem panosu (hesap, pozisyonlar, piyasa, AKIŞ) var.\n' +
|
|
@@ -1769,6 +1851,10 @@ class Engine {
|
|
|
1769
1851
|
'- Emin olmadığında İŞLEM YOK — "BEKLE: <sebep>" yaz. Sık işlem > iyi işlem.\n' +
|
|
1770
1852
|
'- Hesap kaldıracı ve serbest marja göre pozisyon boyutunu düşük tut; tek işlemde serbest marjın büyük kısmını riske atma.\n' +
|
|
1771
1853
|
(session && session.financeStrategy ? `SAHİBİNİN STRATEJİ NOTU (önceliklidir):\n${session.financeStrategy}\n` : '') +
|
|
1854
|
+
(skList.length
|
|
1855
|
+
? '# SKILLS\nKurulu skill kataloğu: görev bir skill\u2019in tanımına uyarsa skill aracıyla gövdesini (SKILL.md) yükleyip uygula — tekerleği yeniden icat etme. Analysis/rapor/PDF gibi işlerde skill\u2019in prosedürüne uy.\n' +
|
|
1856
|
+
skList.map((s) => `- ${s.name}: ${s.description} [${s.path}]`).join('\n') + '\n'
|
|
1857
|
+
: '') +
|
|
1772
1858
|
'RAPOR DİSİPLİNİ: kısa ve sayısal — sembol, yön, lot, giriş, SL/TP, sebep tek satırda. Uzun fal açma; tablo/liste kullanabilirsin.\n' +
|
|
1773
1859
|
`Yerel zaman: ${localDate} ${localTime} — piyasa saatlerine dikkat et (borsa/metal seansları kapalıysa spread genişler; hafta sonu FX kapalıdır).\n` +
|
|
1774
1860
|
(proj ? '# PROJE TALİMATLARI\n' + proj + '\n' : '') +
|
|
@@ -3325,6 +3411,16 @@ class Engine {
|
|
|
3325
3411
|
toolsList = await mcp.mergeTools(toolsList);
|
|
3326
3412
|
/* Beast Apps: kurulu app'lerin araçları (app__<id>__<tool>) modele açılır */
|
|
3327
3413
|
toolsList = apps.mergeTools(toolsList);
|
|
3414
|
+
/* Beast Finance: skill aracı da açık — kataloğun SKILL.md gövdesi
|
|
3415
|
+
modele açılır (skill handler'ı zaten tüm oturumlar için çalışır,
|
|
3416
|
+
definition yalnız BC'de vardı; finance chat copilot + trader ikisi de)
|
|
3417
|
+
Not: BC oturumları zaten opencode registry'sinden skill'i görür. */
|
|
3418
|
+
if (session && session.finance) {
|
|
3419
|
+
const skillDef = (opencode.toolmap.definitions() || []).find(
|
|
3420
|
+
(d) => d && d.function && d.function.name === 'skill'
|
|
3421
|
+
);
|
|
3422
|
+
if (skillDef) toolsList = [...toolsList, skillDef];
|
|
3423
|
+
}
|
|
3328
3424
|
/* panel_run yalnız SANDBOX oturumlarında görünsün — diğer panellerde
|
|
3329
3425
|
modelin alet çantasında olmasın (hook olmadan çalışmaz) */
|
|
3330
3426
|
if (!(session && session.sbSandbox)) {
|
|
@@ -4576,6 +4672,26 @@ const skills = require('./skills');
|
|
|
4576
4672
|
return null;
|
|
4577
4673
|
}
|
|
4578
4674
|
|
|
4675
|
+
/* opencode skill aracı gövdesi: katalogdan eşleşen SKILL.md'yi okur.
|
|
4676
|
+
Salt-okur — tüm oturum türlerinde (BC/finance/chat/trader) aynı yol. */
|
|
4677
|
+
_skillBody(args) {
|
|
4678
|
+
const wanted = String((args && args.name) || '').trim();
|
|
4679
|
+
let list = [];
|
|
4680
|
+
try { list = skills.scan(); } catch {}
|
|
4681
|
+
const hit = list.find((s) => s && s.name === wanted) || null;
|
|
4682
|
+
if (!hit) {
|
|
4683
|
+
return {
|
|
4684
|
+
ok: false,
|
|
4685
|
+
error:
|
|
4686
|
+
'skill bulunamadı: ' + (wanted || '(boş)') +
|
|
4687
|
+
(list.length ? ' — mevcut: ' + list.map((s) => s.name).join(', ') : ''),
|
|
4688
|
+
};
|
|
4689
|
+
}
|
|
4690
|
+
let body = '';
|
|
4691
|
+
try { body = fs.readFileSync(hit.path, 'utf8'); } catch {}
|
|
4692
|
+
return { ok: true, name: hit.name, path: hit.path, content: body.slice(0, 24000) };
|
|
4693
|
+
}
|
|
4694
|
+
|
|
4579
4695
|
async _execTool(name, args, signal, sessionId) {
|
|
4580
4696
|
try {
|
|
4581
4697
|
/* opencode mantığı (BC): model opencode builtin dilinde çağırır —
|
|
@@ -4592,6 +4708,12 @@ const skills = require('./skills');
|
|
|
4592
4708
|
/* ANA KOD KİLİDİ: korumalı bölgeye yazma/silme girişimi daha kapıdan geçmez */
|
|
4593
4709
|
const blocked = this._guardTool(name, args);
|
|
4594
4710
|
if (blocked) return blocked;
|
|
4711
|
+
/* opencode skill aracı: katalogdaki SKILL.md gövdesini modele açar.
|
|
4712
|
+
Salt-okur ve güvenli — BC dahil TÜM oturum türlerinde çalışır
|
|
4713
|
+
(Beast Finance chat copilot + trader buradan kullanır). */
|
|
4714
|
+
if (name === 'skill') {
|
|
4715
|
+
return JSON.stringify(this._skillBody(args));
|
|
4716
|
+
}
|
|
4595
4717
|
if (isBc) {
|
|
4596
4718
|
/* opencode permission akışı (tool/external-directory.ts + her aracın
|
|
4597
4719
|
ctx.ask'ı): önce workspace DIŞI erişim izni, sonra araç izni.
|
|
@@ -4618,24 +4740,6 @@ const skills = require('./skills');
|
|
|
4618
4740
|
} catch (permErr) {
|
|
4619
4741
|
return JSON.stringify({ ok: false, error: AskService.errorText(permErr) });
|
|
4620
4742
|
}
|
|
4621
|
-
/* opencode skill aracı: katalogdaki SKILL.md gövdesini modele açar */
|
|
4622
|
-
if (name === 'skill') {
|
|
4623
|
-
const wanted = String((args && args.name) || '').trim();
|
|
4624
|
-
let list = [];
|
|
4625
|
-
try { list = skills.scan(); } catch {}
|
|
4626
|
-
const hit = list.find((s) => s && s.name === wanted) || null;
|
|
4627
|
-
if (!hit) {
|
|
4628
|
-
return JSON.stringify({
|
|
4629
|
-
ok: false,
|
|
4630
|
-
error:
|
|
4631
|
-
'skill bulunamadı: ' + (wanted || '(boş)') +
|
|
4632
|
-
(list.length ? ' — mevcut: ' + list.map((s) => s.name).join(', ') : ''),
|
|
4633
|
-
});
|
|
4634
|
-
}
|
|
4635
|
-
let body = '';
|
|
4636
|
-
try { body = fs.readFileSync(hit.path, 'utf8'); } catch {}
|
|
4637
|
-
return JSON.stringify({ ok: true, name: hit.name, path: hit.path, content: body.slice(0, 24000) });
|
|
4638
|
-
}
|
|
4639
4743
|
} else {
|
|
4640
4744
|
/* onay kapısı: riskli araçta dış onay bekle; reddedilirse araç çalışmaz.
|
|
4641
4745
|
"always" onaylı araçlar doğrudan geçer. (BC dışı oturumlar — opencode
|
|
@@ -4682,6 +4786,9 @@ const skills = require('./skills');
|
|
|
4682
4786
|
return JSON.stringify(await mcp.call(name, args, signal));
|
|
4683
4787
|
}
|
|
4684
4788
|
if (name === 'memory_write') {
|
|
4789
|
+
/* Supermemory (lokal) DEFAULT: her yazım asenkron push edilir —
|
|
4790
|
+
sunucu ayaktaysa graf motoru özet/çelişki/unutma işlerini yürütür */
|
|
4791
|
+
this._smPush(sessionId, args.text);
|
|
4685
4792
|
/* bot oturumu → botun KENDİ hafıza store'una yaz (global Beast hafızasına değil).
|
|
4686
4793
|
mem0 açıkken: hash+semantic dedup'lı store'a gider, MEMORY.md aynası güncellenir. */
|
|
4687
4794
|
const bctx = this._sessionBotCtx(sessionId ? this.cache.get(String(sessionId)) : null);
|
|
@@ -4690,7 +4797,7 @@ const skills = require('./skills');
|
|
|
4690
4797
|
const r = await mem0.add('bot:' + bctx.id, [args.text]).catch(() => null);
|
|
4691
4798
|
if (r && r.ok) {
|
|
4692
4799
|
mem0.syncMirror('bot:' + bctx.id);
|
|
4693
|
-
return JSON.stringify({ ok: true, duplicate: r.skipped > 0, event: r.events[0] && r.events[0].event });
|
|
4800
|
+
return JSON.stringify({ ok: true, duplicate: r.skipped > 0, event: r.events[0] && r.events[0].event, supermemory: !!this._smConfig() });
|
|
4694
4801
|
}
|
|
4695
4802
|
}
|
|
4696
4803
|
return JSON.stringify(this.botMemory.append(bctx.id, args.text));
|
|
@@ -4699,7 +4806,7 @@ const skills = require('./skills');
|
|
|
4699
4806
|
const r = await mem0.add('main', [args.text]).catch(() => null);
|
|
4700
4807
|
if (r && r.ok) {
|
|
4701
4808
|
mem0.syncMirror('main');
|
|
4702
|
-
return JSON.stringify({ ok: true, duplicate: r.skipped > 0, event: r.events[0] && r.events[0].event });
|
|
4809
|
+
return JSON.stringify({ ok: true, duplicate: r.skipped > 0, event: r.events[0] && r.events[0].event, supermemory: !!this._smConfig() });
|
|
4703
4810
|
}
|
|
4704
4811
|
}
|
|
4705
4812
|
return JSON.stringify(memory.append(args.text));
|
|
@@ -4717,6 +4824,12 @@ const skills = require('./skills');
|
|
|
4717
4824
|
const bctx = this._sessionBotCtx(sessionId ? this.cache.get(String(sessionId)) : null);
|
|
4718
4825
|
const q = String(args.query || '');
|
|
4719
4826
|
const limit = clamp(Number(args.limit) || 5, 1, 15);
|
|
4827
|
+
/* Supermemory DEFAULT öncelik: hibrit arama + kullanıcı profili.
|
|
4828
|
+
Erişilemediyse/hata yerse yerel yollar aynen çalışır (fallback). */
|
|
4829
|
+
const smRes = await this._smSearch(sessionId, q, limit);
|
|
4830
|
+
if (smRes && smRes.results.length) {
|
|
4831
|
+
return JSON.stringify({ ok: true, query: q, ...smRes });
|
|
4832
|
+
}
|
|
4720
4833
|
if (bctx && this.botMemory) {
|
|
4721
4834
|
const rows = this.mem0Enabled
|
|
4722
4835
|
? await mem0.search('bot:' + bctx.id, q, { limit }).catch(() => [])
|
|
@@ -5883,6 +5996,25 @@ Engine.prototype.undoLastTodo = function (sid) {
|
|
|
5883
5996
|
return this.undoTodo(sid, j[j.length - 1].todoId);
|
|
5884
5997
|
};
|
|
5885
5998
|
|
|
5999
|
+
/* /deltodo: oturumun todo listesini tamamen temizler. Dosyaya BOŞ t:todo
|
|
6000
|
+
kaydı append edilir — yüklemede son kayıt kazanır, restart sonrası da
|
|
6001
|
+
boş kalır (eski t:todo satırları yeniden canlanamaz). */
|
|
6002
|
+
Engine.prototype.clearTodos = function (sid) {
|
|
6003
|
+
const key = String(sid || '');
|
|
6004
|
+
try {
|
|
6005
|
+
if (!key || !fs.existsSync(this._file(key))) return { ok: false, error: 'oturum bulunamadı' };
|
|
6006
|
+
} catch {
|
|
6007
|
+
return { ok: false, error: 'oturum bulunamadı' };
|
|
6008
|
+
}
|
|
6009
|
+
const prev = this.todos.get(key) || [];
|
|
6010
|
+
this.todos.set(key, []);
|
|
6011
|
+
try {
|
|
6012
|
+
fs.appendFileSync(this._file(key), JSON.stringify({ t: 'todo', items: [] }) + '\n');
|
|
6013
|
+
} catch {}
|
|
6014
|
+
emitSafe(this, key, { type: 'todos', sessionId: key, todos: [] });
|
|
6015
|
+
return { ok: true, count: prev.length };
|
|
6016
|
+
};
|
|
6017
|
+
|
|
5886
6018
|
module.exports = Engine;
|
|
5887
6019
|
module.exports.Engine = Engine;
|
|
5888
6020
|
module.exports.sanitizeTodoItems = sanitizeTodoItems;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/* ---------- Supermemory (lokal) adaptörü ----------
|
|
4
|
+
Beast'in bellek katmanına supermemory (git.new/memory) köprüsü — mem0.js
|
|
5
|
+
deseniyle TAKILABİLİR katman: sunucu (npx supermemory local → localhost:6767)
|
|
6
|
+
ayaktayken daha akıllı bellek (graf motoru, otomatik unutma, çelişki
|
|
7
|
+
çözümü, kullanıcı profili); ayakta değilken engine mevcut hafızaya sorunsuz
|
|
8
|
+
düşer. Zero npm bağımlılık — Node 18+ global fetch.
|
|
9
|
+
|
|
10
|
+
Uçlar (supermemory docs birebir):
|
|
11
|
+
POST /v3/documents { content, containerTag, customId?, metadata? }
|
|
12
|
+
POST /v4/search { q, containerTag, searchMode, limit, include? }
|
|
13
|
+
POST /v4/profile { containerTag, q? }
|
|
14
|
+
Auth: Authorization: Bearer <apiKey> (local key ilk boot'ta basılır) */
|
|
15
|
+
|
|
16
|
+
const BASE_TIMEOUT_MS = 8000;
|
|
17
|
+
const PROBE_TIMEOUT_MS = 2500;
|
|
18
|
+
const HEALTH_COOLDOWN_MS = 60000; // kapalıya aynı dakikada 200 kez vurma
|
|
19
|
+
|
|
20
|
+
function normalizeBase(url) {
|
|
21
|
+
return String(url || '').trim().replace(/\/+$/, '');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function call(baseUrl, apiKey, path, body, timeoutMs) {
|
|
25
|
+
const base = normalizeBase(baseUrl);
|
|
26
|
+
if (!base) throw new Error('supermemory baseUrl boş');
|
|
27
|
+
const ctrl = new AbortController();
|
|
28
|
+
const timer = setTimeout(() => ctrl.abort(), timeoutMs || BASE_TIMEOUT_MS);
|
|
29
|
+
try {
|
|
30
|
+
const res = await fetch(base + path, {
|
|
31
|
+
method: 'POST',
|
|
32
|
+
headers: {
|
|
33
|
+
'content-type': 'application/json',
|
|
34
|
+
...(apiKey ? { authorization: 'Bearer ' + apiKey } : {}),
|
|
35
|
+
},
|
|
36
|
+
body: JSON.stringify(body || {}),
|
|
37
|
+
signal: ctrl.signal,
|
|
38
|
+
});
|
|
39
|
+
if (!res.ok) {
|
|
40
|
+
const txt = await res.text().catch(() => '');
|
|
41
|
+
throw new Error('HTTP ' + res.status + (txt ? ': ' + txt.slice(0, 180) : ''));
|
|
42
|
+
}
|
|
43
|
+
return await res.json();
|
|
44
|
+
} finally {
|
|
45
|
+
clearTimeout(timer);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* Sunucu ayakta mı? GET / ile ucuz sağlık kontrolü */
|
|
50
|
+
async function probe(baseUrl, apiKey) {
|
|
51
|
+
const base = normalizeBase(baseUrl);
|
|
52
|
+
if (!base) return false;
|
|
53
|
+
const ctrl = new AbortController();
|
|
54
|
+
const timer = setTimeout(() => ctrl.abort(), PROBE_TIMEOUT_MS);
|
|
55
|
+
try {
|
|
56
|
+
const res = await fetch(base, {
|
|
57
|
+
method: 'GET',
|
|
58
|
+
headers: apiKey ? { authorization: 'Bearer ' + apiKey } : {},
|
|
59
|
+
signal: ctrl.signal,
|
|
60
|
+
});
|
|
61
|
+
return res.status < 500;
|
|
62
|
+
} catch {
|
|
63
|
+
return false;
|
|
64
|
+
} finally {
|
|
65
|
+
clearTimeout(timer);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* Konuşma/belgi yut — arka planda çağrılır (fire-and-forget desenine uygun) */
|
|
70
|
+
async function add(baseUrl, apiKey, { content, containerTag, customId, metadata }) {
|
|
71
|
+
return call(baseUrl, apiKey, '/v3/documents', {
|
|
72
|
+
content: String(content || ''),
|
|
73
|
+
containerTag: String(containerTag || 'beast'),
|
|
74
|
+
...(customId ? { customId: String(customId) } : {}),
|
|
75
|
+
...(metadata && typeof metadata === 'object' ? { metadata } : {}),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* Hibrit arama → normalize: [{text, similarity}] — memories varsayılan;
|
|
80
|
+
'hybrid' RAG + bellek tek sorguda */
|
|
81
|
+
async function search(baseUrl, apiKey, { q, containerTag, limit, mode }) {
|
|
82
|
+
const r = await call(
|
|
83
|
+
baseUrl,
|
|
84
|
+
apiKey,
|
|
85
|
+
'/v4/search',
|
|
86
|
+
{
|
|
87
|
+
q: String(q || ''),
|
|
88
|
+
containerTag: String(containerTag || 'beast'),
|
|
89
|
+
searchMode: mode === 'documents' || mode === 'hybrid' ? mode : 'memories',
|
|
90
|
+
limit: Math.min(20, Math.max(1, Number(limit) || 5)),
|
|
91
|
+
},
|
|
92
|
+
12000
|
|
93
|
+
);
|
|
94
|
+
const out = [];
|
|
95
|
+
for (const hit of (r && Array.isArray(r.results) ? r.results : []).slice(0, 10)) {
|
|
96
|
+
if (hit && typeof hit.memory === 'string' && hit.memory.trim()) {
|
|
97
|
+
out.push({ text: hit.memory.trim().slice(0, 600), similarity: Number(hit.similarity) || 0 });
|
|
98
|
+
}
|
|
99
|
+
/* hybrid mod: chunk'lar da gelir */
|
|
100
|
+
for (const c of (hit && Array.isArray(hit.chunks) ? hit.chunks : []).slice(0, 3)) {
|
|
101
|
+
const t = c && (c.content || c.text);
|
|
102
|
+
if (typeof t === 'string' && t.trim()) {
|
|
103
|
+
out.push({ text: t.trim().slice(0, 600), similarity: Number(c && c.similarity) || 0 });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return out.slice(0, Math.max(1, Number(limit) || 5));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* Kullanıcı profili: { static: [], dynamic: [] } — her turda ucuz bağlam */
|
|
111
|
+
async function profile(baseUrl, apiKey, { containerTag, q }) {
|
|
112
|
+
const r = await call(
|
|
113
|
+
baseUrl,
|
|
114
|
+
apiKey,
|
|
115
|
+
'/v4/profile',
|
|
116
|
+
{
|
|
117
|
+
containerTag: String(containerTag || 'beast'),
|
|
118
|
+
...(q ? { q: String(q) } : {}),
|
|
119
|
+
},
|
|
120
|
+
12000
|
|
121
|
+
);
|
|
122
|
+
const p = (r && r.profile) || {};
|
|
123
|
+
const arr = (x) =>
|
|
124
|
+
Array.isArray(x)
|
|
125
|
+
? x.map((s) => String(s || '').trim()).filter(Boolean).slice(0, 12)
|
|
126
|
+
: typeof x === 'string' && x.trim()
|
|
127
|
+
? [x.trim().slice(0, 300)]
|
|
128
|
+
: [];
|
|
129
|
+
return { static: arr(p.static), dynamic: arr(p.dynamic) };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
module.exports = { probe, add, search, profile, HEALTH_COOLDOWN_MS };
|
package/src/agent/watchers.js
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
kind=logs → Beast log dosyası taranır; son windowMin dakikadaki error/warn sayısı
|
|
7
7
|
değer olur ("10 dk içinde 3+ hata olursa bağır")
|
|
8
8
|
Koşul sağlanınca ilgili oturuma mesaj düşer (WA köprüsüne de otomatik akar).
|
|
9
|
-
Depo: %APPDATA%\beast\watchers.json
|
|
9
|
+
Depo: %APPDATA%\beast\watchers.json
|
|
10
|
+
Loglar: %APPDATA%\beast\watcher-logs.json (ana depodan AYRI; izleyici başına denetim kaydı) */
|
|
10
11
|
|
|
11
12
|
const fs = require('fs');
|
|
12
13
|
const path = require('path');
|
|
@@ -18,7 +19,89 @@ function file() {
|
|
|
18
19
|
return path.join(beastRoot(), 'watchers.json');
|
|
19
20
|
}
|
|
20
21
|
|
|
22
|
+
/* İzleyici logları ANA depodan AYRI tutulur: watcher-logs.json
|
|
23
|
+
Yapı: { logs: { [watcherId]: [{ ts, ev: ok|trigger|error, value, error }] } }
|
|
24
|
+
İzleyici başına LOG_CAP kayıt tutulur (yenisi eklenince en eskisi düşer). */
|
|
25
|
+
const LOG_CAP = 200;
|
|
26
|
+
const LOG_FILE = 'watcher-logs.json';
|
|
27
|
+
|
|
28
|
+
function logFile() {
|
|
29
|
+
return path.join(beastRoot(), LOG_FILE);
|
|
30
|
+
}
|
|
31
|
+
|
|
21
32
|
let items = [];
|
|
33
|
+
let logs = {}; // watcherId -> [{ ts, ev, value, error }]
|
|
34
|
+
let logsDirty = false;
|
|
35
|
+
|
|
36
|
+
function loadLogs() {
|
|
37
|
+
try {
|
|
38
|
+
const raw = JSON.parse(fs.readFileSync(logFile(), 'utf8'));
|
|
39
|
+
logs = raw && typeof raw.logs === 'object' && raw.logs !== null ? raw.logs : {};
|
|
40
|
+
} catch {
|
|
41
|
+
logs = {};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function saveLogs(force) {
|
|
46
|
+
if (!logsDirty && !force) return;
|
|
47
|
+
try {
|
|
48
|
+
fs.mkdirSync(beastRoot(), { recursive: true });
|
|
49
|
+
fs.writeFileSync(logFile(), JSON.stringify({ logs }, null, 2));
|
|
50
|
+
logsDirty = false;
|
|
51
|
+
} catch {}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* Tek izleyiciye log kaydı düş — değer/seri hale getirilemezse güvenli temsil */
|
|
55
|
+
function pushLog(id, ev, value, error) {
|
|
56
|
+
let v = null;
|
|
57
|
+
if (value !== undefined && value !== null) {
|
|
58
|
+
try {
|
|
59
|
+
v = typeof value === 'object' ? JSON.stringify(value).slice(0, 200) : value;
|
|
60
|
+
} catch {
|
|
61
|
+
v = String(value).slice(0, 200);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const arr = Array.isArray(logs[id]) ? logs[id] : (logs[id] = []);
|
|
65
|
+
arr.push({
|
|
66
|
+
ts: new Date().toISOString(),
|
|
67
|
+
ev, // 'ok' | 'trigger' | 'error'
|
|
68
|
+
value: v,
|
|
69
|
+
error: error ? String(error).slice(0, 300) : '',
|
|
70
|
+
});
|
|
71
|
+
if (arr.length > LOG_CAP) arr.splice(0, arr.length - LOG_CAP);
|
|
72
|
+
logsDirty = true;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* Bir izleyicinin logları — en yeni en başta */
|
|
76
|
+
function logEntries(id) {
|
|
77
|
+
const arr = Array.isArray(logs[String(id)]) ? logs[String(id)] : [];
|
|
78
|
+
return arr.slice().reverse();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* Tüm izleyicilerin logları — { [watcherId]: [...] }, en yeni en başta */
|
|
82
|
+
function logsAll() {
|
|
83
|
+
const out = {};
|
|
84
|
+
for (const k of Object.keys(logs)) out[k] = logEntries(k);
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* Logları temizle — id verilirse yalnız o izleyici, yoksa hepsi */
|
|
89
|
+
function logClear(id) {
|
|
90
|
+
if (id) delete logs[String(id)];
|
|
91
|
+
else logs = {};
|
|
92
|
+
saveLogs(true);
|
|
93
|
+
return { ok: true };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* Artık var olmayan izleyicilerin loglarını temizle */
|
|
97
|
+
function pruneLogs() {
|
|
98
|
+
const ids = new Set(items.map((w) => w.id));
|
|
99
|
+
let changed = false;
|
|
100
|
+
for (const k of Object.keys(logs)) {
|
|
101
|
+
if (!ids.has(k)) { delete logs[k]; changed = true; }
|
|
102
|
+
}
|
|
103
|
+
if (changed) saveLogs(true);
|
|
104
|
+
}
|
|
22
105
|
|
|
23
106
|
function load() {
|
|
24
107
|
try {
|
|
@@ -27,6 +110,8 @@ function load() {
|
|
|
27
110
|
} catch {
|
|
28
111
|
items = [];
|
|
29
112
|
}
|
|
113
|
+
loadLogs();
|
|
114
|
+
pruneLogs();
|
|
30
115
|
}
|
|
31
116
|
|
|
32
117
|
function save() {
|
|
@@ -189,6 +274,7 @@ function remove(id) {
|
|
|
189
274
|
const before = items.length;
|
|
190
275
|
items = items.filter((w) => w.id !== id);
|
|
191
276
|
save();
|
|
277
|
+
if (items.length < before) logClear(id); /* izleyici gitti → logları da gitsin */
|
|
192
278
|
return { ok: items.length < before };
|
|
193
279
|
}
|
|
194
280
|
|
|
@@ -283,6 +369,7 @@ async function tickOnce(deps = {}) {
|
|
|
283
369
|
const r = applyCheck(w, value, now);
|
|
284
370
|
Object.assign(w, r.patch);
|
|
285
371
|
w.lastError = '';
|
|
372
|
+
pushLog(w.id, r.triggered ? 'trigger' : 'ok', value);
|
|
286
373
|
if (r.triggered) {
|
|
287
374
|
if (typeof hooks.onTrigger === 'function') hooks.onTrigger({ ...w }, value);
|
|
288
375
|
events.push({ id: w.id, name: w.name, value });
|
|
@@ -290,9 +377,11 @@ async function tickOnce(deps = {}) {
|
|
|
290
377
|
} catch (e) {
|
|
291
378
|
w.lastCheckAt = new Date(now).toISOString();
|
|
292
379
|
w.lastError = String((e && e.message) || e).slice(0, 300);
|
|
380
|
+
pushLog(w.id, 'error', undefined, w.lastError);
|
|
293
381
|
}
|
|
294
382
|
}
|
|
295
383
|
save();
|
|
384
|
+
saveLogs();
|
|
296
385
|
return events;
|
|
297
386
|
}
|
|
298
387
|
|
|
@@ -330,4 +419,7 @@ module.exports = {
|
|
|
330
419
|
applyCheck,
|
|
331
420
|
isDue,
|
|
332
421
|
cooldownActive,
|
|
422
|
+
logEntries,
|
|
423
|
+
logsAll,
|
|
424
|
+
logClear,
|
|
333
425
|
};
|
package/src/cron.js
CHANGED
|
@@ -136,12 +136,13 @@ function list() {
|
|
|
136
136
|
return jobs.map((j) => ({ ...j }));
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
function add({ name, schedule, prompt, once, sessionId }) {
|
|
139
|
+
function add({ name, schedule, prompt, once, sessionId, kind }) {
|
|
140
140
|
const n = String(name || '').trim().slice(0, 80) || 'Görev';
|
|
141
141
|
const s = String(schedule || '').trim();
|
|
142
142
|
const p = String(prompt || '').trim().slice(0, 4000);
|
|
143
143
|
if (!isValidSchedule(s)) return { ok: false, error: 'geçersiz cron ifadesi (örn: */15 * * * *)' };
|
|
144
144
|
if (!p) return { ok: false, error: 'görev metni boş olamaz' };
|
|
145
|
+
const k = String(kind || '').trim().slice(0, 24);
|
|
145
146
|
const job = {
|
|
146
147
|
id: uid(),
|
|
147
148
|
name: n,
|
|
@@ -150,6 +151,7 @@ function add({ name, schedule, prompt, once, sessionId }) {
|
|
|
150
151
|
sessionId: sessionId || null,
|
|
151
152
|
once: !!once,
|
|
152
153
|
enabled: true,
|
|
154
|
+
...(k ? { kind: k } : {}),
|
|
153
155
|
createdAt: new Date().toISOString(),
|
|
154
156
|
lastRunAt: null,
|
|
155
157
|
nextRunAt: nextRunFrom(s, new Date()),
|
|
@@ -186,6 +188,17 @@ function remove(id) {
|
|
|
186
188
|
return { ok: jobs.length < before };
|
|
187
189
|
}
|
|
188
190
|
|
|
191
|
+
/* Koşula uyan TÜM görevleri sil (/autodel, "hatırlatmaları sil"). */
|
|
192
|
+
function removeIf(pred) {
|
|
193
|
+
const before = jobs.length;
|
|
194
|
+
jobs = jobs.filter((j) => {
|
|
195
|
+
try { return !pred(j); } catch { return true; }
|
|
196
|
+
});
|
|
197
|
+
const count = before - jobs.length;
|
|
198
|
+
if (count) save();
|
|
199
|
+
return { ok: true, count };
|
|
200
|
+
}
|
|
201
|
+
|
|
189
202
|
/* Tüm görevleri bellekten ve diskten sil; zamanlayıcıyı durdur.
|
|
190
203
|
"hepsini sil" / "/cron clear" için kullanılır. */
|
|
191
204
|
function clearAll() {
|
|
@@ -233,17 +246,43 @@ function tick() {
|
|
|
233
246
|
}
|
|
234
247
|
}
|
|
235
248
|
|
|
249
|
+
/* Uygulama kapalıyken saati geçen tek-seferlik hatırlatma için teslim penceresi:
|
|
250
|
+
bu kadar süre geçmediyse uyarı GECİKMELİ de olsa teslim edilir. */
|
|
251
|
+
const ONCE_GRACE_MS = 60 * 60 * 1000;
|
|
252
|
+
|
|
253
|
+
/* Açılış uzlaşması (saf): saati geçmiş tek-seferlik hatırlatmalar eski
|
|
254
|
+
davranışta nextRunFrom ile GELECEK YILIN aynı gününe yeniden kuruluyordu —
|
|
255
|
+
bayat kayıt ölümsüzleşip tekrar tekrar önüne düşüyordu. Artık: grace
|
|
256
|
+
içindeyse "fire" listesine (gecikmeli teslim), bayatsa listeden düşer.
|
|
257
|
+
Tekrarlıların nextRunAt'ı her zamanki gibi yeniden hesaplanır. */
|
|
258
|
+
function reconcile(list, now, graceMs) {
|
|
259
|
+
const keep = [];
|
|
260
|
+
const fire = [];
|
|
261
|
+
for (const job of list) {
|
|
262
|
+
if (!job.enabled) { keep.push(job); continue; }
|
|
263
|
+
const nr = job.nextRunAt ? new Date(job.nextRunAt).getTime() : 0;
|
|
264
|
+
if (job.once && nr && nr <= now) {
|
|
265
|
+
if (now - nr <= graceMs) fire.push(job);
|
|
266
|
+
continue; // bayat olan burada düşer
|
|
267
|
+
}
|
|
268
|
+
if (!nr || nr <= now) {
|
|
269
|
+
keep.push({ ...job, nextRunAt: nextRunFrom(job.schedule, new Date(now)) });
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
keep.push(job);
|
|
273
|
+
}
|
|
274
|
+
return { jobs: keep, fire };
|
|
275
|
+
}
|
|
276
|
+
|
|
236
277
|
function init(hooks) {
|
|
237
278
|
onFire = (hooks && hooks.onFire) || null;
|
|
238
279
|
load();
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
job.nextRunAt = nextRunFrom(job.schedule, new Date());
|
|
242
|
-
}
|
|
243
|
-
}
|
|
280
|
+
const r = reconcile(jobs, Date.now(), ONCE_GRACE_MS);
|
|
281
|
+
jobs = r.jobs;
|
|
244
282
|
save();
|
|
245
283
|
if (timer) clearInterval(timer);
|
|
246
284
|
timer = setInterval(tick, 20 * 1000);
|
|
285
|
+
for (const job of r.fire) fire(job); // grace içindeki bayat hatırlatmayı gecikmeli teslim et
|
|
247
286
|
}
|
|
248
287
|
|
|
249
288
|
/* /stop: zamanlayıcıyı durdur (init ile geri başlar) */
|
|
@@ -252,4 +291,4 @@ function stop() {
|
|
|
252
291
|
timer = null;
|
|
253
292
|
}
|
|
254
293
|
|
|
255
|
-
module.exports = { init, stop, list, add, update, remove, clearAll, toggle, runNow, parseCron, nextRunFrom, isValidSchedule, reminderSchedule };
|
|
294
|
+
module.exports = { init, stop, list, add, update, remove, removeIf, clearAll, toggle, runNow, parseCron, nextRunFrom, isValidSchedule, reminderSchedule, reconcile, ONCE_GRACE_MS };
|
package/src/main.js
CHANGED
|
@@ -426,6 +426,11 @@ function parseSettingsText(text) {
|
|
|
426
426
|
const parsed = JSON.parse(text);
|
|
427
427
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new Error('bozuk yapı');
|
|
428
428
|
if (!parsed.roleModels) parsed.roleModels = {};
|
|
429
|
+
/* Supermemory (lokal) DEFAULT AÇIK — sunucu ayaktayken daha akıllı bellek;
|
|
430
|
+
ayakta değilken klasik hafıza sorunsuz çalışır (engine fallback) */
|
|
431
|
+
if (!parsed.supermemory || typeof parsed.supermemory !== 'object') {
|
|
432
|
+
parsed.supermemory = { enabled: true, baseUrl: 'http://localhost:6767', apiKey: '', containerTag: 'beast' };
|
|
433
|
+
}
|
|
429
434
|
return parsed;
|
|
430
435
|
}
|
|
431
436
|
|
|
@@ -1101,6 +1106,8 @@ function waSlashHelp() {
|
|
|
1101
1106
|
'• */usage* – bugünkü kullanım',
|
|
1102
1107
|
'• */backup* – tüm veriyi ŞİFRELİ yedekle (Beast Kodu imzalı, Masaüstü\\Beast-Backups)',
|
|
1103
1108
|
'• */status* – bağlantı ve servis durumu',
|
|
1109
|
+
'• */autodel* – tüm otomatik hatırlatmaları (bayat/eski dahil) tek komutla sil · */autodel all*: cron görevleri dahil hepsi',
|
|
1110
|
+
'• */deltodo* – bu oturumun todo listesini temizle · */deltodo all*: tüm oturumların todoları',
|
|
1104
1111
|
'',
|
|
1105
1112
|
'Gruplarda beni @mention ile çağır; ardışık mesajlarını tek cevapta birleştiririm.',
|
|
1106
1113
|
].join('\n');
|
|
@@ -1641,6 +1648,58 @@ async function tryWaSlash(jid, rawText, senderNum, payload0) {
|
|
|
1641
1648
|
} else {
|
|
1642
1649
|
out = 'Kullanım: `/cron list` · `/cron clear` (hepsini sil) · `/cron del <id>`';
|
|
1643
1650
|
}
|
|
1651
|
+
} else if (cmd === 'autodel') {
|
|
1652
|
+
/* /autodel [all] — TÜM otomatik hatırlatmaları tek komutla sil.
|
|
1653
|
+
Bayat/eski kayıtlar dahil: kind=reminder + eski isim/prompt önekleri eşleşir.
|
|
1654
|
+
/autodel all → cron görevleri dahil her şey (=/cron clear) */
|
|
1655
|
+
const sub = String(arg || '').toLowerCase().trim();
|
|
1656
|
+
if (sub === 'all' || sub === 'hepsi' || sub === 'hepsini') {
|
|
1657
|
+
const r = cron.clearAll();
|
|
1658
|
+
cronEmit();
|
|
1659
|
+
out = r.ok
|
|
1660
|
+
? `*Tüm zamanlanmış görevler silindi* (${r.count} adet).`
|
|
1661
|
+
: 'Temizlenemedi.';
|
|
1662
|
+
} else {
|
|
1663
|
+
const r = cron.removeIf(isReminderJob);
|
|
1664
|
+
cronEmit();
|
|
1665
|
+
const left = cron.list().filter((j) => j.enabled).length;
|
|
1666
|
+
out = r.count
|
|
1667
|
+
? `*${r.count} otomatik hatırlatma silindi.* Kalan aktif görev: ${left}`
|
|
1668
|
+
: 'Silinecek otomatik hatırlatma yok — zaten temiz.';
|
|
1669
|
+
}
|
|
1670
|
+
} else if (cmd === 'deltodo') {
|
|
1671
|
+
/* /deltodo [all] — todo listelerini temizler: arg=all → TÜM oturumlar;
|
|
1672
|
+
yoksa yalnız bu sohbetin oturumu. Panelden de düşsün diye 'todos'
|
|
1673
|
+
olayı emit edilir (engine.clearTodos). */
|
|
1674
|
+
const sub = String(arg || '').toLowerCase().trim();
|
|
1675
|
+
if (sub === 'all' || sub === 'hepsi' || sub === 'hepsini') {
|
|
1676
|
+
let n = 0;
|
|
1677
|
+
let items = 0;
|
|
1678
|
+
try {
|
|
1679
|
+
for (const v of engine.listSessions()) {
|
|
1680
|
+
const r = engine.clearTodos(v.id);
|
|
1681
|
+
if (r && r.ok) {
|
|
1682
|
+
n++;
|
|
1683
|
+
items += r.count || 0;
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
} catch {}
|
|
1687
|
+
out = n
|
|
1688
|
+
? `*Tüm oturumların todoları temizlendi* (${n} oturum, ${items} madde).`
|
|
1689
|
+
: 'Temizlenecek todo yok.';
|
|
1690
|
+
} else {
|
|
1691
|
+
const sid = waChats.get(jid) || waSingleSid || '';
|
|
1692
|
+
if (!sid) {
|
|
1693
|
+
out = 'Bu sohbette açık oturum yok — todo listesi de yok.';
|
|
1694
|
+
} else {
|
|
1695
|
+
const r = engine.clearTodos(sid);
|
|
1696
|
+
out = r.ok
|
|
1697
|
+
? (r.count
|
|
1698
|
+
? `*${r.count} todo temizlendi.* (tüm oturumlar için: /deltodo all)`
|
|
1699
|
+
: 'Todo listesi zaten boş. (tüm oturumlar için: /deltodo all)')
|
|
1700
|
+
: 'Oturum bulunamadı — todo listesi yok.';
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1644
1703
|
} else if (cmd === 'beastcode') {
|
|
1645
1704
|
/* /beastcode [görev] — WhatsApp'tan UZAKTAN KODLAMA modu:
|
|
1646
1705
|
masaüstünde GERÇEK Beast Code paneli açılır (IDE ekranı), sohbet
|
|
@@ -2079,6 +2138,22 @@ async function documentToTextAsync(media) {
|
|
|
2079
2138
|
|
|
2080
2139
|
/* ---------- WA hatırlatıcı ---------- */
|
|
2081
2140
|
|
|
2141
|
+
/* Hatırlatma job'ı mı? Yeni kayıtlarda kind='reminder' var; eski (bayat)
|
|
2142
|
+
kayıtlar isim/prompt önekinden tanınır — /autodel ve doğal dil silme
|
|
2143
|
+
bunu kullanır. */
|
|
2144
|
+
function isReminderJob(j) {
|
|
2145
|
+
if (!j || typeof j !== 'object') return false;
|
|
2146
|
+
if (j.kind === 'reminder') return true;
|
|
2147
|
+
const n = String(j.name || '');
|
|
2148
|
+
const p = String(j.prompt || '');
|
|
2149
|
+
return (
|
|
2150
|
+
/^Hatırlatma:/.test(n) ||
|
|
2151
|
+
/^Tekrarlı hatırlatma:/.test(n) ||
|
|
2152
|
+
p.startsWith('[HATIRLATMA ZAMANI]') ||
|
|
2153
|
+
p.startsWith('[TEKRARLI HATIRLATMA]')
|
|
2154
|
+
);
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2082
2157
|
function scheduleReminder({ when, message, sessionId, repeat }) {
|
|
2083
2158
|
try {
|
|
2084
2159
|
const msg = String(message || '').trim();
|
|
@@ -2092,6 +2167,7 @@ function scheduleReminder({ when, message, sessionId, repeat }) {
|
|
|
2092
2167
|
name: 'Tekrarlı hatırlatma: ' + msg.slice(0, 40),
|
|
2093
2168
|
schedule: s.schedule,
|
|
2094
2169
|
prompt: `[TEKRARLI HATIRLATMA] Kullanıcıya şunu hatırlat: "${msg}". Kısaca ve nazikçe bildir.`,
|
|
2170
|
+
kind: 'reminder',
|
|
2095
2171
|
sessionId: sessionId || undefined,
|
|
2096
2172
|
});
|
|
2097
2173
|
if (!r.ok) return r;
|
|
@@ -2106,6 +2182,7 @@ function scheduleReminder({ when, message, sessionId, repeat }) {
|
|
|
2106
2182
|
name: 'Hatırlatma: ' + msg.slice(0, 40),
|
|
2107
2183
|
schedule: cronExpr,
|
|
2108
2184
|
prompt: `[HATIRLATMA ZAMANI] Kullanıcıya şunu hatırlat: "${msg}". Kısaca ve nazikçe bildir.`,
|
|
2185
|
+
kind: 'reminder',
|
|
2109
2186
|
once: true,
|
|
2110
2187
|
sessionId: sessionId || undefined,
|
|
2111
2188
|
});
|
|
@@ -2286,6 +2363,23 @@ async function handleWaIncoming(jid, payload, senderNum) {
|
|
|
2286
2363
|
return;
|
|
2287
2364
|
}
|
|
2288
2365
|
|
|
2366
|
+
/* "hatırlatmaları sil" / "alarm temizle" / "reminders kaldır" → yalnızca
|
|
2367
|
+
otomatik hatırlatmalar silinir (cron görevlerine dokunulmaz). */
|
|
2368
|
+
if (
|
|
2369
|
+
/hat[iı]rlat|alarm|remind/.test(lc) &&
|
|
2370
|
+
/(sil|sıl|temizle|kaldır|iptal|sıfırla|kapat)/.test(lc)
|
|
2371
|
+
) {
|
|
2372
|
+
const r = cron.removeIf(isReminderJob);
|
|
2373
|
+
cronEmit();
|
|
2374
|
+
await sendWaSafe(
|
|
2375
|
+
jid,
|
|
2376
|
+
r.count
|
|
2377
|
+
? `*${r.count} otomatik hatırlatma silindi.* Zamanlanmış kalan görev: ${cron.list().filter((j) => j.enabled).length}`
|
|
2378
|
+
: 'Silinecek otomatik hatırlatma yok — zaten temiz.'
|
|
2379
|
+
).catch(() => {});
|
|
2380
|
+
return;
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2289
2383
|
if (isGroup) {
|
|
2290
2384
|
const g = settings.waGroups || {};
|
|
2291
2385
|
if (!g.enabled) return;
|
|
@@ -3075,6 +3169,10 @@ function reloadBackend() {
|
|
|
3075
3169
|
nightReflectAt: settings.nightReflectAt || null,
|
|
3076
3170
|
approvals: settings.security && settings.security.approvals ? approvalsBridge : null,
|
|
3077
3171
|
alwaysAllowTools: (settings.security && settings.security.alwaysAllow) || [],
|
|
3172
|
+
/* Supermemory (lokal) bellek katmanı — default açıktır (parseSettingsText);
|
|
3173
|
+
sunucu ayakta değilse engine klasik hafızaya düşer. Kapamak:
|
|
3174
|
+
settings.json → supermemory.enabled = false */
|
|
3175
|
+
supermemory: settings.supermemory || { enabled: true, baseUrl: 'http://localhost:6767', apiKey: '', containerTag: 'beast' },
|
|
3078
3176
|
crashFile: FALLOUT_CRASH_FILE,
|
|
3079
3177
|
notifyOwnerFail: settings.notifyOwnerFail !== false,
|
|
3080
3178
|
fileSend: deliverFile,
|
|
@@ -3298,11 +3396,11 @@ function reloadBackend() {
|
|
|
3298
3396
|
}
|
|
3299
3397
|
/* CRON → TÜM AKTİF ENTEGRASYONLAR: cron işi bittiğinde cevap (ya da
|
|
3300
3398
|
hata) sahibin bağlı olduğu her kanala yansıtılır — WA + Telegram +
|
|
3301
|
-
Discord. Aynı kanal hem cron oturumuna bağlıysa TEK cevap alır.
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
if (!ev.aborted) {
|
|
3399
|
+
Discord. Aynı kanal hem cron oturumuna bağlıysa TEK cevap alır.
|
|
3400
|
+
Bayat (TTL aşımı) bekleme kaydı Take ile düşer, yansıtılmaz. */
|
|
3401
|
+
if (ev.type === 'done' || ev.type === 'error') {
|
|
3402
|
+
const cjob = cronAnswerPendingTake(ev.sessionId);
|
|
3403
|
+
if (cjob && !ev.aborted) {
|
|
3306
3404
|
(async () => {
|
|
3307
3405
|
try {
|
|
3308
3406
|
let txt = '';
|
|
@@ -6030,6 +6128,39 @@ ipcMain.handle('sec:set', (_e, cfg) => {
|
|
|
6030
6128
|
log.info('sec', `güvenlik: onay kapısı ${settings.security.approvals ? 'AÇIK' : 'KAPALI (her şey serbest)'}`);
|
|
6031
6129
|
return { approvals: settings.security.approvals, alwaysAllow: settings.security.alwaysAllow };
|
|
6032
6130
|
});
|
|
6131
|
+
|
|
6132
|
+
/* Supermemory (lokal) ayarları: engine'e canlı yansıt + sağlık önbelleğini tazele */
|
|
6133
|
+
ipcMain.handle('supermemory:set', (_e, cfg) => {
|
|
6134
|
+
try {
|
|
6135
|
+
const base = String((cfg && cfg.baseUrl) || '').trim() || 'http://localhost:6767';
|
|
6136
|
+
settings.supermemory = {
|
|
6137
|
+
enabled: !!(cfg && cfg.enabled),
|
|
6138
|
+
baseUrl: base.replace(/\/+$/, ''),
|
|
6139
|
+
apiKey: String((cfg && cfg.apiKey) || '').trim(),
|
|
6140
|
+
containerTag: String((cfg && cfg.containerTag) || 'beast').trim() || 'beast',
|
|
6141
|
+
};
|
|
6142
|
+
saveSettings();
|
|
6143
|
+
if (engine) {
|
|
6144
|
+
engine.supermemory = settings.supermemory;
|
|
6145
|
+
engine._smUp = null;
|
|
6146
|
+
engine._smCheckedAt = 0;
|
|
6147
|
+
engine._smWarned = false;
|
|
6148
|
+
}
|
|
6149
|
+
return { ok: true, supermemory: settings.supermemory };
|
|
6150
|
+
} catch (e) {
|
|
6151
|
+
return { ok: false, error: String((e && e.message) || e) };
|
|
6152
|
+
}
|
|
6153
|
+
});
|
|
6154
|
+
ipcMain.handle('supermemory:get', () => {
|
|
6155
|
+
const sm = settings.supermemory || {};
|
|
6156
|
+
return {
|
|
6157
|
+
enabled: sm.enabled !== false,
|
|
6158
|
+
baseUrl: sm.baseUrl || 'http://localhost:6767',
|
|
6159
|
+
apiKey: sm.apiKey || '',
|
|
6160
|
+
containerTag: sm.containerTag || 'beast',
|
|
6161
|
+
up: engine ? !!engine._smUp : null,
|
|
6162
|
+
};
|
|
6163
|
+
});
|
|
6033
6164
|
ipcMain.handle('approval:respond', (_e, { id, ok, always }) => resolveApproval(id, ok, always));
|
|
6034
6165
|
/* opencode permission reply (BC): UI kartı üçlü cevap verir —
|
|
6035
6166
|
'once' | 'always' | 'reject' (+ opsiyonel geri bildirim) */
|
|
@@ -6477,8 +6608,40 @@ function cronEmit() {
|
|
|
6477
6608
|
|
|
6478
6609
|
/* Cron cevabı bekleme haritası: sid -> job. İş 'done' olunca cevap
|
|
6479
6610
|
SAHİBİN bağlı olduğu TÜM AKTİF entegrasyonlara yansıtılır (kullanıcı
|
|
6480
|
-
hangi kanaldan ajanla iletişimde belli değil).
|
|
6611
|
+
hangi kanaldan ajanla iletişimde belli değil). Kayıtlar TTL'li —
|
|
6612
|
+
done/error hiç gelmezse bayat bekleme sonraki done'a yansıtılmaz. */
|
|
6481
6613
|
const cronAnswerPending = new Map();
|
|
6614
|
+
const cronAnswerPendingAt = new Map();
|
|
6615
|
+
const CRON_ANSWER_TTL_MS = 30 * 60 * 1000;
|
|
6616
|
+
|
|
6617
|
+
function cronAnswerPendingSet(sid, job) {
|
|
6618
|
+
cronAnswerPendingSweep();
|
|
6619
|
+
cronAnswerPending.set(String(sid), job);
|
|
6620
|
+
cronAnswerPendingAt.set(String(sid), Date.now());
|
|
6621
|
+
}
|
|
6622
|
+
|
|
6623
|
+
function cronAnswerPendingDrop(sid) {
|
|
6624
|
+
cronAnswerPending.delete(String(sid));
|
|
6625
|
+
cronAnswerPendingAt.delete(String(sid));
|
|
6626
|
+
}
|
|
6627
|
+
|
|
6628
|
+
/* Bayat kayıt (TTL aşımı) haritalardan temizlenir */
|
|
6629
|
+
function cronAnswerPendingSweep() {
|
|
6630
|
+
const now = Date.now();
|
|
6631
|
+
for (const [k, at] of cronAnswerPendingAt) {
|
|
6632
|
+
if (now - at > CRON_ANSWER_TTL_MS) cronAnswerPendingDrop(k);
|
|
6633
|
+
}
|
|
6634
|
+
}
|
|
6635
|
+
|
|
6636
|
+
/* Kaydı okuyup düşürür; bayat kayıt ise null döner (yansıtma yok) */
|
|
6637
|
+
function cronAnswerPendingTake(sid) {
|
|
6638
|
+
const k = String(sid);
|
|
6639
|
+
const job = cronAnswerPending.get(k);
|
|
6640
|
+
const at = cronAnswerPendingAt.get(k) || 0;
|
|
6641
|
+
cronAnswerPendingDrop(k);
|
|
6642
|
+
if (!job || Date.now() - at > CRON_ANSWER_TTL_MS) return null;
|
|
6643
|
+
return job;
|
|
6644
|
+
}
|
|
6482
6645
|
|
|
6483
6646
|
/* OTOMATİK YENİ SOHBET YOK: cron/izleyici/fallout tetiklendiğinde oturum
|
|
6484
6647
|
seçimi — kayıtlı id'nin DOSYASI hâlâ duruyorsa O, değilse EN GÜNCEL
|
|
@@ -6558,11 +6721,11 @@ function cronFire(job) {
|
|
|
6558
6721
|
if (sid !== String(job.sessionId || '')) {
|
|
6559
6722
|
cron.update(job.id, { sessionId: sid });
|
|
6560
6723
|
}
|
|
6561
|
-
|
|
6724
|
+
cronAnswerPendingSet(sid, job);
|
|
6562
6725
|
const sent = engine.send(sid, {
|
|
6563
6726
|
text: `[cron: ${job.name}]\n${job.prompt}`,
|
|
6564
6727
|
});
|
|
6565
|
-
if (!sent)
|
|
6728
|
+
if (!sent) cronAnswerPendingDrop(sid); // gönderilemedi — bayat bekleme bırakma
|
|
6566
6729
|
} catch {}
|
|
6567
6730
|
cronEmit();
|
|
6568
6731
|
}
|
|
@@ -7082,6 +7245,9 @@ ipcMain.handle('watchers:toggle', (_e, id) => {
|
|
|
7082
7245
|
watchers.patch(String(id), { enabled: !w.enabled });
|
|
7083
7246
|
return { ok: true, watcher: watchers.get(String(id)) };
|
|
7084
7247
|
});
|
|
7248
|
+
/* izleyici logları — ana depodan ayrı watcher-logs.json üzerinden */
|
|
7249
|
+
ipcMain.handle('watchers:logs', () => watchers.logsAll());
|
|
7250
|
+
ipcMain.handle('watchers:logsClear', (_e, id) => watchers.logClear(id ? String(id) : null));
|
|
7085
7251
|
ipcMain.handle('cron:add', (_e, job) => {
|
|
7086
7252
|
const r = cron.add(job || {});
|
|
7087
7253
|
cronEmit();
|
package/src/preload.js
CHANGED
|
@@ -89,6 +89,9 @@ contextBridge.exposeInMainWorld('beast', {
|
|
|
89
89
|
bcDeleteAll: () => ipcRenderer.invoke('bc:deleteAll'),
|
|
90
90
|
/* opencode permission (BC): UI kartı cevabı — 'once' | 'always' | 'reject' */
|
|
91
91
|
permissionReply: (requestId, action, message) => ipcRenderer.invoke('permission:reply', { requestId, action, message }),
|
|
92
|
+
/* Supermemory (lokal) bellek katmanı ayarları */
|
|
93
|
+
supermemoryGet: () => ipcRenderer.invoke('supermemory:get'),
|
|
94
|
+
supermemorySet: (cfg) => ipcRenderer.invoke('supermemory:set', cfg),
|
|
92
95
|
studioSend: (msg, attachments) => ipcRenderer.invoke('studio:send', { msg, attachments }),
|
|
93
96
|
studioStop: () => ipcRenderer.invoke('studio:stop'),
|
|
94
97
|
studioNew: () => ipcRenderer.invoke('studio:new'),
|
|
@@ -143,6 +146,10 @@ contextBridge.exposeInMainWorld('beast', {
|
|
|
143
146
|
|
|
144
147
|
watchersToggle: (id) => ipcRenderer.invoke('watchers:toggle', id),
|
|
145
148
|
|
|
149
|
+
watchersLogs: () => ipcRenderer.invoke('watchers:logs'),
|
|
150
|
+
|
|
151
|
+
watchersLogsClear: (id) => ipcRenderer.invoke('watchers:logsClear', id),
|
|
152
|
+
|
|
146
153
|
providerKeys: () => ipcRenderer.invoke('providers:keys'),
|
|
147
154
|
|
|
148
155
|
openPath: (p) => ipcRenderer.invoke('shell:openPath', p),
|
package/src/renderer/i18n.js
CHANGED
|
@@ -542,6 +542,14 @@
|
|
|
542
542
|
bot_no_watchers: 'Bu bota ait izleyici yok.',
|
|
543
543
|
w_pane: 'İZLEYİCİLER',
|
|
544
544
|
w_empty: 'Kayıtlı izleyici yok.',
|
|
545
|
+
tipWatchLog: 'İzleyici loglarını gör',
|
|
546
|
+
w_log_title: 'İzleyici Logları',
|
|
547
|
+
w_log_clear: 'Seçili izleyicinin loglarını temizle',
|
|
548
|
+
w_log_cleared: 'Loglar temizlendi',
|
|
549
|
+
w_log_empty: 'Bu izleyici için henüz log kaydı yok.',
|
|
550
|
+
w_log_ev_ok: 'kontrol',
|
|
551
|
+
w_log_ev_trigger: 'tetik',
|
|
552
|
+
w_log_ev_error: 'hata',
|
|
545
553
|
bot_stats_sub: 'Bu botun kullanım özeti.',
|
|
546
554
|
bot_stat_numbers: 'Numara',
|
|
547
555
|
bot_stat_sessions: 'Sohbet',
|
|
@@ -1038,6 +1046,14 @@
|
|
|
1038
1046
|
cr_toggle_pause: 'Pause',
|
|
1039
1047
|
w_empty: 'No saved watchers — add from the form above.',
|
|
1040
1048
|
w_pane: 'WATCHERS',
|
|
1049
|
+
tipWatchLog: 'View watcher logs',
|
|
1050
|
+
w_log_title: 'Watcher Logs',
|
|
1051
|
+
w_log_clear: 'Clear selected watcher logs',
|
|
1052
|
+
w_log_cleared: 'Logs cleared',
|
|
1053
|
+
w_log_empty: 'No log entries for this watcher yet.',
|
|
1054
|
+
w_log_ev_ok: 'check',
|
|
1055
|
+
w_log_ev_trigger: 'trigger',
|
|
1056
|
+
w_log_ev_error: 'error',
|
|
1041
1057
|
wa_vazgec: 'Cancel',
|
|
1042
1058
|
wa_kaydet: 'save',
|
|
1043
1059
|
wa_updated: 'Updated',
|
package/src/renderer/index.html
CHANGED
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
<div id="watchPaneHead">
|
|
18
18
|
<span id="watchPaneTitle" data-i18n="w_pane">İZLEYİCİLER</span>
|
|
19
19
|
<span class="file-spacer"></span>
|
|
20
|
-
<button id="
|
|
20
|
+
<button id="watchPaneLog" title="İzleyici loglarını gör" data-i18n-title="tipWatchLog"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M10 9H8"/><path d="M16 13H8"/><path d="M16 17H8"/></svg></button>
|
|
21
|
+
<button id="watchPaneOpen" title="İzleyicileri yönet" data-i18n-title="tipWatch">+</button>
|
|
21
22
|
</div>
|
|
22
23
|
<div id="watchPaneList"></div>
|
|
23
24
|
</div>
|
|
@@ -528,6 +529,22 @@
|
|
|
528
529
|
</div>
|
|
529
530
|
</div>
|
|
530
531
|
|
|
532
|
+
<div id="watchLogOverlay" class="mini-overlay" hidden>
|
|
533
|
+
<div class="mini-dialog">
|
|
534
|
+
<div class="mini-head">
|
|
535
|
+
<span class="mini-title" data-i18n="w_log_title">İzleyici Logları</span>
|
|
536
|
+
<button class="mini-close" id="watchLogClose" title="Kapat" data-i18n-title="btn_close">×</button>
|
|
537
|
+
</div>
|
|
538
|
+
<div class="mini-body">
|
|
539
|
+
<div id="watchLogBar">
|
|
540
|
+
<select id="watchLogSel" class="mini-select"></select>
|
|
541
|
+
<button id="watchLogClear" class="mr-btn" title="Seçili izleyicinin loglarını temizle" data-i18n-title="w_log_clear"><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 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/></svg></button>
|
|
542
|
+
</div>
|
|
543
|
+
<div id="watchLogList" class="mini-list"></div>
|
|
544
|
+
</div>
|
|
545
|
+
</div>
|
|
546
|
+
</div>
|
|
547
|
+
|
|
531
548
|
<div id="finSymOverlay" class="mini-overlay" hidden>
|
|
532
549
|
<div class="mini-dialog fin-sym-dialog">
|
|
533
550
|
<div class="mini-head">
|
package/src/renderer/renderer.js
CHANGED
|
@@ -45,6 +45,7 @@ const els = {
|
|
|
45
45
|
watchBtn: $('#watchBtn'),
|
|
46
46
|
watchPaneList: $('#watchPaneList'),
|
|
47
47
|
watchPaneOpen: $('#watchPaneOpen'),
|
|
48
|
+
watchPaneLog: $('#watchPaneLog'),
|
|
48
49
|
cronBtn: $('#cronBtn'),
|
|
49
50
|
watchOverlay: $('#watchOverlay'),
|
|
50
51
|
cronOverlay: $('#cronOverlay'),
|
|
@@ -52,6 +53,11 @@ const els = {
|
|
|
52
53
|
cronClose: $('#cronClose'),
|
|
53
54
|
watchList: $('#watchList'),
|
|
54
55
|
cronModalList: $('#cronModalList'),
|
|
56
|
+
watchLogOverlay: $('#watchLogOverlay'),
|
|
57
|
+
watchLogClose: $('#watchLogClose'),
|
|
58
|
+
watchLogSel: $('#watchLogSel'),
|
|
59
|
+
watchLogClear: $('#watchLogClear'),
|
|
60
|
+
watchLogList: $('#watchLogList'),
|
|
55
61
|
browserBar: $('#browserBar'),
|
|
56
62
|
bbBack: $('#bbBack'),
|
|
57
63
|
bbFwd: $('#bbFwd'),
|
|
@@ -5611,6 +5617,11 @@ async function renderWatchersPane(force) {
|
|
|
5611
5617
|
`</div></div>`;
|
|
5612
5618
|
const btns = document.createElement('div');
|
|
5613
5619
|
btns.className = 'wp-btns';
|
|
5620
|
+
const lg = document.createElement('button');
|
|
5621
|
+
lg.className = 'wp-btn';
|
|
5622
|
+
lg.innerHTML = '<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="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M10 9H8"/><path d="M16 13H8"/><path d="M16 17H8"/></svg>';
|
|
5623
|
+
lg.title = _t('w_log_title');
|
|
5624
|
+
lg.addEventListener('click', (e) => { e.stopPropagation(); openWatchLogModal(w.id); });
|
|
5614
5625
|
const t = document.createElement('button');
|
|
5615
5626
|
t.className = 'wp-btn';
|
|
5616
5627
|
t.textContent = w.enabled ? '❚❚' : '▶';
|
|
@@ -5632,7 +5643,7 @@ async function renderWatchersPane(force) {
|
|
|
5632
5643
|
renderWatchersPane(true);
|
|
5633
5644
|
renderWatchersModal(true);
|
|
5634
5645
|
});
|
|
5635
|
-
btns.append(t, d);
|
|
5646
|
+
btns.append(lg, t, d);
|
|
5636
5647
|
row.appendChild(btns);
|
|
5637
5648
|
row.addEventListener('click', () => openWatchModal());
|
|
5638
5649
|
list.appendChild(row);
|
|
@@ -5669,6 +5680,11 @@ async function renderWatchersModal(force) {
|
|
|
5669
5680
|
`</div></div>`;
|
|
5670
5681
|
const btns = document.createElement('div');
|
|
5671
5682
|
btns.style.cssText = 'display:flex;gap:6px;flex:none';
|
|
5683
|
+
const lg = document.createElement('button');
|
|
5684
|
+
lg.className = 'mr-btn';
|
|
5685
|
+
lg.innerHTML = '<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="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M10 9H8"/><path d="M16 13H8"/><path d="M16 17H8"/></svg>';
|
|
5686
|
+
lg.title = _t('w_log_title');
|
|
5687
|
+
lg.addEventListener('click', () => openWatchLogModal(w.id));
|
|
5672
5688
|
const t = document.createElement('button');
|
|
5673
5689
|
t.className = 'mr-btn';
|
|
5674
5690
|
t.textContent = w.enabled ? '❚❚' : '▶';
|
|
@@ -5679,12 +5695,85 @@ async function renderWatchersModal(force) {
|
|
|
5679
5695
|
d.textContent = '×';
|
|
5680
5696
|
d.title = _t('cr_del');
|
|
5681
5697
|
d.addEventListener('click', async () => { await beast.watchersRemove(w.id); renderWatchersModal(); });
|
|
5682
|
-
btns.append(t, d);
|
|
5698
|
+
btns.append(lg, t, d);
|
|
5683
5699
|
row.appendChild(btns);
|
|
5684
5700
|
els.watchList.appendChild(row);
|
|
5685
5701
|
}
|
|
5686
5702
|
}
|
|
5687
5703
|
|
|
5704
|
+
/* ---------- İZLEYİCİ LOG MODALI ----------
|
|
5705
|
+
Her izleyicinin logu AYRI tutulur (watcher-logs.json) ve AYRI gösterilir:
|
|
5706
|
+
üstteki seçiciyle izleyici seç → yalnız onun denetim kayıtları listelenir. */
|
|
5707
|
+
let watchLogSelId = '';
|
|
5708
|
+
let lastWatchLogKey = '';
|
|
5709
|
+
let lastWatchLogSelKey = '';
|
|
5710
|
+
|
|
5711
|
+
async function renderWatchLogModal(force) {
|
|
5712
|
+
if (!els.watchLogOverlay) return;
|
|
5713
|
+
let rows = []; try { rows = (await beast.watchersList()) || []; } catch {}
|
|
5714
|
+
let logsMap = {}; try { logsMap = (await beast.watchersLogs()) || {}; } catch {}
|
|
5715
|
+
/* seçim: yok olan seçilmişse ilk izleyiciye düş */
|
|
5716
|
+
if (!rows.some((w) => w.id === watchLogSelId)) watchLogSelId = rows.length ? rows[0].id : '';
|
|
5717
|
+
/* seçici — izleyici listesi değişmediyse DOM'a dokunma (odak korunur) */
|
|
5718
|
+
const selKey = JSON.stringify(rows.map((w) => [w.id, w.name, w.enabled]));
|
|
5719
|
+
if (selKey !== lastWatchLogSelKey) {
|
|
5720
|
+
lastWatchLogSelKey = selKey;
|
|
5721
|
+
els.watchLogSel.innerHTML = '';
|
|
5722
|
+
if (!rows.length) {
|
|
5723
|
+
const o = document.createElement('option');
|
|
5724
|
+
o.textContent = _t('w_empty');
|
|
5725
|
+
o.value = '';
|
|
5726
|
+
els.watchLogSel.appendChild(o);
|
|
5727
|
+
} else {
|
|
5728
|
+
for (const w of rows) {
|
|
5729
|
+
const o = document.createElement('option');
|
|
5730
|
+
o.value = w.id;
|
|
5731
|
+
o.textContent = (w.enabled ? '' : '⏸ ') + w.name;
|
|
5732
|
+
els.watchLogSel.appendChild(o);
|
|
5733
|
+
}
|
|
5734
|
+
}
|
|
5735
|
+
els.watchLogSel.value = watchLogSelId;
|
|
5736
|
+
}
|
|
5737
|
+
/* log satırları — yalnız seçili izleyicinin kayıtları */
|
|
5738
|
+
const entries = watchLogSelId ? (logsMap[watchLogSelId] || []) : [];
|
|
5739
|
+
const key = JSON.stringify([watchLogSelId, selKey, entries]);
|
|
5740
|
+
if (!force && key === lastWatchLogKey) return;
|
|
5741
|
+
lastWatchLogKey = key;
|
|
5742
|
+
els.watchLogList.innerHTML = '';
|
|
5743
|
+
if (!watchLogSelId || !entries.length) {
|
|
5744
|
+
els.watchLogList.innerHTML = '<div class="mini-empty">' + _t('w_log_empty') + '</div>';
|
|
5745
|
+
return;
|
|
5746
|
+
}
|
|
5747
|
+
const evBadge = { ok: ['ok', _t('w_log_ev_ok')], trigger: ['trigger', _t('w_log_ev_trigger')], error: ['error', _t('w_log_ev_error')] };
|
|
5748
|
+
for (const en of entries) {
|
|
5749
|
+
const [cls, label] = evBadge[en.ev] || ['ok', en.ev];
|
|
5750
|
+
const row = document.createElement('div');
|
|
5751
|
+
row.className = 'wl-row';
|
|
5752
|
+
row.innerHTML =
|
|
5753
|
+
`<span class="wl-time">${escapeHtml(fmtWhen(en.ts))}</span>` +
|
|
5754
|
+
`<span class="wl-badge ${cls}">${escapeHtml(label)}</span>` +
|
|
5755
|
+
`<span class="wl-main">` +
|
|
5756
|
+
(en.ev === 'error'
|
|
5757
|
+
? `<span class="wl-err">${escapeHtml(String(en.error || '').slice(0, 120))}</span>`
|
|
5758
|
+
: (en.value !== null && en.value !== undefined ? escapeHtml(String(en.value).slice(0, 120)) : '—')) +
|
|
5759
|
+
`</span>`;
|
|
5760
|
+
els.watchLogList.appendChild(row);
|
|
5761
|
+
}
|
|
5762
|
+
}
|
|
5763
|
+
|
|
5764
|
+
function closeWatchLogModal() {
|
|
5765
|
+
toggleMini(els.watchLogOverlay, true);
|
|
5766
|
+
stopMiniRefresh();
|
|
5767
|
+
}
|
|
5768
|
+
|
|
5769
|
+
async function openWatchLogModal(id) {
|
|
5770
|
+
if (id) watchLogSelId = String(id);
|
|
5771
|
+
lastWatchLogSelKey = ''; /* seçici her açılışta TAZE kurulsun */
|
|
5772
|
+
toggleMini(els.watchLogOverlay, false);
|
|
5773
|
+
await renderWatchLogModal(true);
|
|
5774
|
+
startMiniRefresh();
|
|
5775
|
+
}
|
|
5776
|
+
|
|
5688
5777
|
let lastCronKey = '';
|
|
5689
5778
|
async function renderCronModal(force) {
|
|
5690
5779
|
let jobs = [];
|
|
@@ -5746,6 +5835,7 @@ function startMiniRefresh() {
|
|
|
5746
5835
|
stopMiniRefresh();
|
|
5747
5836
|
miniRefreshTimer = setInterval(async () => {
|
|
5748
5837
|
if (els.watchOverlay && !els.watchOverlay.hidden) await renderWatchersModal();
|
|
5838
|
+
else if (els.watchLogOverlay && !els.watchLogOverlay.hidden) await renderWatchLogModal();
|
|
5749
5839
|
else if (els.cronOverlay && !els.cronOverlay.hidden) await renderCronModal();
|
|
5750
5840
|
else stopMiniRefresh();
|
|
5751
5841
|
}, 4000);
|
|
@@ -6487,12 +6577,29 @@ async function init() {
|
|
|
6487
6577
|
/* cron modülü — olay bağlama */
|
|
6488
6578
|
if (els.watchBtn) els.watchBtn.addEventListener('click', openWatchModal);
|
|
6489
6579
|
if (els.watchPaneOpen) els.watchPaneOpen.addEventListener('click', openWatchModal);
|
|
6580
|
+
if (els.watchPaneLog) els.watchPaneLog.addEventListener('click', () => openWatchLogModal());
|
|
6490
6581
|
if (els.cronBtn) els.cronBtn.addEventListener('click', openCronModal);
|
|
6491
6582
|
if (els.watchClose) els.watchClose.addEventListener('click', closeWatchModal);
|
|
6492
6583
|
if (els.cronClose) els.cronClose.addEventListener('click', closeCronModal);
|
|
6584
|
+
if (els.watchLogClose) els.watchLogClose.addEventListener('click', closeWatchLogModal);
|
|
6585
|
+
if (els.watchLogSel) els.watchLogSel.addEventListener('change', () => {
|
|
6586
|
+
watchLogSelId = els.watchLogSel.value;
|
|
6587
|
+
lastWatchLogKey = '';
|
|
6588
|
+
renderWatchLogModal(true);
|
|
6589
|
+
});
|
|
6590
|
+
if (els.watchLogClear) els.watchLogClear.addEventListener('click', async () => {
|
|
6591
|
+
if (!watchLogSelId) return;
|
|
6592
|
+
await beast.watchersLogsClear(watchLogSelId);
|
|
6593
|
+
lastWatchLogKey = '';
|
|
6594
|
+
await renderWatchLogModal(true);
|
|
6595
|
+
toast(_t('w_log_cleared'));
|
|
6596
|
+
});
|
|
6493
6597
|
if (els.watchOverlay) els.watchOverlay.addEventListener('click', (e) => {
|
|
6494
6598
|
if (e.target === els.watchOverlay) closeWatchModal();
|
|
6495
6599
|
});
|
|
6600
|
+
if (els.watchLogOverlay) els.watchLogOverlay.addEventListener('click', (e) => {
|
|
6601
|
+
if (e.target === els.watchLogOverlay) closeWatchLogModal();
|
|
6602
|
+
});
|
|
6496
6603
|
if (els.cronOverlay) els.cronOverlay.addEventListener('click', (e) => {
|
|
6497
6604
|
if (e.target === els.cronOverlay) closeCronModal();
|
|
6498
6605
|
});
|
|
@@ -6579,6 +6686,7 @@ async function init() {
|
|
|
6579
6686
|
document.addEventListener('keydown', (e) => {
|
|
6580
6687
|
if (e.key === 'Escape') {
|
|
6581
6688
|
if ($('#botOverlay') && !$('#botOverlay').hidden) { botOverlaySetOpen(false); return; }
|
|
6689
|
+
if (els.watchLogOverlay && !els.watchLogOverlay.hidden) { closeWatchLogModal(); return; }
|
|
6582
6690
|
if (els.watchOverlay && !els.watchOverlay.hidden) { closeWatchModal(); return; }
|
|
6583
6691
|
if (els.cronOverlay && !els.cronOverlay.hidden) { closeCronModal(); return; }
|
|
6584
6692
|
if (!els.settingsOverlay.hidden) closeSettings();
|
package/src/renderer/style.css
CHANGED
|
@@ -133,7 +133,6 @@ body:not(.ide-mode):not(.studio-mode):not(.sandbox-mode) #watchPane { display: f
|
|
|
133
133
|
}
|
|
134
134
|
#watchPaneTitle { font-size: 10.5px; letter-spacing: 2px; color: var(--muted); font-weight: 800; }
|
|
135
135
|
#watchPaneHead button {
|
|
136
|
-
margin-left: auto;
|
|
137
136
|
background: none;
|
|
138
137
|
border: 1px solid var(--border);
|
|
139
138
|
border-radius: 7px;
|
|
@@ -142,7 +141,13 @@ body:not(.ide-mode):not(.studio-mode):not(.sandbox-mode) #watchPane { display: f
|
|
|
142
141
|
font-size: 12px;
|
|
143
142
|
line-height: 1.2;
|
|
144
143
|
padding: 1px 7px;
|
|
144
|
+
height: 22px;
|
|
145
|
+
display: inline-flex;
|
|
146
|
+
align-items: center;
|
|
147
|
+
justify-content: center;
|
|
145
148
|
}
|
|
149
|
+
/* LOG ve + butonları sağda YAN YANA BİTİŞİK dursun: yalnız ilkinde itici boşluk */
|
|
150
|
+
#watchPaneHead button:first-of-type { margin-left: auto; }
|
|
146
151
|
#watchPaneHead button:hover { color: var(--text); background: var(--panel2); }
|
|
147
152
|
#watchPaneList { flex: 1; overflow-y: auto; padding: 4px; }
|
|
148
153
|
.wp-row {
|
|
@@ -1464,6 +1469,35 @@ body.browser-open .mini-overlay { right: var(--bw, 480px); }
|
|
|
1464
1469
|
.mr-btn:hover { color: var(--accent); }
|
|
1465
1470
|
.mr-btn.del:hover { color: var(--err); }
|
|
1466
1471
|
|
|
1472
|
+
/* ---------- İzleyici log modalı (her izleyicinin logu ayrı) ---------- */
|
|
1473
|
+
#watchLogBar { display: flex; gap: 6px; align-items: center; margin-bottom: 10px; }
|
|
1474
|
+
#watchLogSel { flex: 1; min-width: 0; }
|
|
1475
|
+
.mini-select {
|
|
1476
|
+
background: var(--panel2);
|
|
1477
|
+
border: 1px solid var(--border);
|
|
1478
|
+
border-radius: 7px;
|
|
1479
|
+
color: var(--text);
|
|
1480
|
+
font-size: 12px;
|
|
1481
|
+
padding: 4px 6px;
|
|
1482
|
+
}
|
|
1483
|
+
.wl-row {
|
|
1484
|
+
display: flex; align-items: baseline; gap: 7px;
|
|
1485
|
+
background: var(--panel); border: 1px solid var(--border);
|
|
1486
|
+
border-radius: 8px; padding: 5px 8px;
|
|
1487
|
+
}
|
|
1488
|
+
.wl-row:hover { background: var(--panel2); }
|
|
1489
|
+
.wl-time { flex: none; color: var(--muted); font-size: 10px; min-width: 56px; }
|
|
1490
|
+
.wl-badge {
|
|
1491
|
+
flex: none; font-size: 9px; font-weight: 800; letter-spacing: 0.5px;
|
|
1492
|
+
padding: 1px 6px; border-radius: 5px; border: 1px solid var(--border);
|
|
1493
|
+
text-transform: uppercase;
|
|
1494
|
+
}
|
|
1495
|
+
.wl-badge.ok { color: var(--muted); }
|
|
1496
|
+
.wl-badge.trigger { color: var(--accent); border-color: var(--accent); }
|
|
1497
|
+
.wl-badge.error { color: var(--err); border-color: var(--err); }
|
|
1498
|
+
.wl-main { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 11.5px; }
|
|
1499
|
+
.wl-err { color: var(--err); font-size: 11px; }
|
|
1500
|
+
|
|
1467
1501
|
/* #26 ajan dosya kartı */
|
|
1468
1502
|
.file-card {
|
|
1469
1503
|
display: flex;
|
|
@@ -1916,6 +1950,7 @@ body.browser-open #settingsDialog { width: min(70vw, calc(100vw - var(--bw, 480p
|
|
|
1916
1950
|
flex: none;
|
|
1917
1951
|
width: 250px;
|
|
1918
1952
|
min-width: 250px;
|
|
1953
|
+
min-height: 0;
|
|
1919
1954
|
background: var(--panel);
|
|
1920
1955
|
border-left: 1px solid var(--border);
|
|
1921
1956
|
display: flex;
|
|
@@ -1957,9 +1992,19 @@ body.browser-open #settingsDialog { width: min(70vw, calc(100vw - var(--bw, 480p
|
|
|
1957
1992
|
}
|
|
1958
1993
|
#railClear:hover { background: rgba(0, 0, 0, 0.08); }
|
|
1959
1994
|
#railClear svg { display: block; }
|
|
1960
|
-
}
|
|
1961
1995
|
|
|
1962
|
-
|
|
1996
|
+
/* rail listesi: kartlar ekranı aştığında KAYDIRILABİLİR olmalı —
|
|
1997
|
+
flex:1 + min-height:0 + overflow-y:auto üçlüsü zorunlu. overscroll-behavior
|
|
1998
|
+
sayfa geri kaydırma animasyonunu da keser. */
|
|
1999
|
+
#railList {
|
|
2000
|
+
flex: 1 1 auto;
|
|
2001
|
+
min-height: 0;
|
|
2002
|
+
overflow-y: auto;
|
|
2003
|
+
overflow-x: hidden;
|
|
2004
|
+
overscroll-behavior: contain;
|
|
2005
|
+
scrollbar-gutter: stable;
|
|
2006
|
+
padding: 4px 8px;
|
|
2007
|
+
}
|
|
1963
2008
|
|
|
1964
2009
|
.rail-empty {
|
|
1965
2010
|
color: var(--muted);
|