beast-agent 2.17.0 → 2.19.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 +219 -28
- package/src/agent/skills.js +1 -1
- package/src/agent/watchers.js +93 -1
- package/src/cron.js +46 -7
- package/src/main.js +457 -69
- package/src/preload.js +7 -0
- package/src/renderer/i18n.js +20 -0
- package/src/renderer/index.html +32 -1
- package/src/renderer/renderer.js +247 -3
- package/src/renderer/style.css +182 -3
package/package.json
CHANGED
package/src/agent/engine.js
CHANGED
|
@@ -359,6 +359,16 @@ class Engine {
|
|
|
359
359
|
/* #17 kalıcı ajan geçmişi: restart sonrası işler + sohbetler kaybolmasın */
|
|
360
360
|
this._bgJobsFile = path.join(this.sessionsDir, 'bg-jobs.json');
|
|
361
361
|
this._loadBgJobsPersisted();
|
|
362
|
+
/* AJAN DM: paralel/finance ajanlarının birbirine attığı mesajlar
|
|
363
|
+
(AJAN DM paneli buradan beslenir; restart sonrası da durur) */
|
|
364
|
+
this._agentDms = [];
|
|
365
|
+
this._agentDmsFile = path.join(this.sessionsDir, 'agent-dms.json');
|
|
366
|
+
try {
|
|
367
|
+
const raw = JSON.parse(fs.readFileSync(this._agentDmsFile, 'utf8'));
|
|
368
|
+
this._agentDms = Array.isArray(raw.dms) ? raw.dms : [];
|
|
369
|
+
} catch {
|
|
370
|
+
this._agentDms = [];
|
|
371
|
+
}
|
|
362
372
|
/* superyorizyon döngüsü: takılan/uzun süren ajanı CEO'ya bildirir */
|
|
363
373
|
this._supTimer = setInterval(() => {
|
|
364
374
|
try { this._supervise(); } catch {}
|
|
@@ -1830,14 +1840,28 @@ class Engine {
|
|
|
1830
1840
|
: '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.'
|
|
1831
1841
|
: '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.';
|
|
1832
1842
|
const proj = this._projectInstructions(session);
|
|
1843
|
+
/* Skills kataloğu: finance oturumlarında da skill'ler çağrılabilir
|
|
1844
|
+
(skill aracı _chatTurn'de finance oturumlarına enjekte edilir). */
|
|
1845
|
+
const skList = (() => {
|
|
1846
|
+
try { return skills.scan(); } catch { return []; }
|
|
1847
|
+
})();
|
|
1848
|
+
/* CHATTEKI BEAST KİMLİĞİ: finance ajanı da sahibin SOUL/USER/KURALLARINI taşır */
|
|
1849
|
+
const mem = memory.loadAll();
|
|
1850
|
+
const rules = memory.listRules();
|
|
1851
|
+
const isWorker = !!(session && session.bgJob && !isTrader);
|
|
1852
|
+
const modeBlock2 = isWorker
|
|
1853
|
+
? 'ROL: FİNANS İŞÇİSİ 🔎 — TEK bir görev için piyasa verisi toplar, analiz eder, RAPOR döndürürsün. mt5_trade/mt5_pending/mt5_close KULLANMA — yalnız okuma araçları (mt5_status/account/market/positions/orders/history). İş bitince kısa final raporu yaz.'
|
|
1854
|
+
: modeBlock;
|
|
1833
1855
|
return (
|
|
1856
|
+
(mem.soul ? mem.soul.trim() + '\n\n' : '') +
|
|
1834
1857
|
'Sen BEAST FİNANS\u2019sın — bilgisayardaki MetaTrader 5 (MT5) terminaline köprüyle bağlı trading ajanı.\n' +
|
|
1835
1858
|
'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' +
|
|
1836
1859
|
'KÖPRÜ: MT5 terminaline Python stdio köprüsüyle bağlısın; panel 3 saniyede bir hesap/pozisyon/piyasa verisini tazeler. mt5_* araç çağrıların ve işlem hareketlerin (açılan/kapanan pozisyonlar) SAĞ paneldeki AKIŞ akışına canlı düşer — panelin orayı izlediğini bil.\n' +
|
|
1837
1860
|
'GÖREV ALANI: piyasa verisi okuma (fiyat/hesap/pozisyon/geçmiş), teknik değerlendirme, pozisyon yönetimi (aç/kapat/SL/TP), risk disiplini ve kısa karar raporları. Kod yazma işi DEĞİLDİR.\n' +
|
|
1838
|
-
|
|
1861
|
+
modeBlock2 + '\n' +
|
|
1839
1862
|
'MT5 ARAÇLARI: mt5_status (bağlantı), mt5_account (hesap), mt5_market (canlı fiyat), mt5_positions (açık pozisyonlar), mt5_orders (bekleyen emirler), mt5_history (kapanan işlemler), mt5_trade (piyasa emri), mt5_close (kapat), mt5_modify (SL/TP), mt5_pending (bekleyen emir), mt5_cancel (emir iptal).\n' +
|
|
1840
1863
|
'VERİ AKIŞI (her değerlendirmede): mt5_account + mt5_positions + mt5_market çağrılarını AYNI turda PARALEL ver; gerekiyorsa mt5_history ile son işlemleri gör.\n' +
|
|
1864
|
+
'PARALEL + KOORDİNASYON: uzun araştırma/işleri run_background ile paralel finance işçisine devret (parent finance olduğu için işçi mt5 okuma araçlarını görür); koşan ajanlarla konuşmak için agent_dm (to: ajan başlığındaki anahtar kelime, örn "GOLD").\n' +
|
|
1841
1865
|
(symbols ? `İZLEME LİSTESİ: ${symbols}\n` : '') +
|
|
1842
1866
|
(lim.maxLot ? `LİMİTLER: max lot ${lim.maxLot}` + (lim.maxPositions ? ` · max eşzamanlı pozisyon ${lim.maxPositions}` : '') + ' — bunları aşıp araç kullanma; sistem zaten reddeder.\n' : '') +
|
|
1843
1867
|
'RİSK DİSİPLİNİ:\n' +
|
|
@@ -1846,6 +1870,12 @@ class Engine {
|
|
|
1846
1870
|
'- Emin olmadığında İŞLEM YOK — "BEKLE: <sebep>" yaz. Sık işlem > iyi işlem.\n' +
|
|
1847
1871
|
'- 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' +
|
|
1848
1872
|
(session && session.financeStrategy ? `SAHİBİNİN STRATEJİ NOTU (önceliklidir):\n${session.financeStrategy}\n` : '') +
|
|
1873
|
+
(mem.user ? `# USER\n${mem.user}\n` : '') +
|
|
1874
|
+
(rules.length ? `# KURALLAR (sahibinin kalıcı talimatları — daima uy)\n${rules.map((r) => '- ' + r).join('\n')}\n` : '') +
|
|
1875
|
+
(skList.length
|
|
1876
|
+
? '# 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' +
|
|
1877
|
+
skList.map((s) => `- ${s.name}: ${s.description} [${s.path}]`).join('\n') + '\n'
|
|
1878
|
+
: '') +
|
|
1849
1879
|
'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' +
|
|
1850
1880
|
`Yerel zaman: ${localDate} ${localTime} — piyasa saatlerine dikkat et (borsa/metal seansları kapalıysa spread genişler; hafta sonu FX kapalıdır).\n` +
|
|
1851
1881
|
(proj ? '# PROJE TALİMATLARI\n' + proj + '\n' : '') +
|
|
@@ -2788,6 +2818,15 @@ class Engine {
|
|
|
2788
2818
|
if (adef && (adef.mode === 'bg' || adef.mode === 'all')) bg.agentName = adef.name;
|
|
2789
2819
|
}
|
|
2790
2820
|
this.cache.set(bg.id, bg);
|
|
2821
|
+
/* FİNANS İŞÇİSİ: parent finance oturumuyken (Beast Finance sohbeti) açılan
|
|
2822
|
+
arka plan işi mt5_* okuma araçlarını görür ve finance promptuyla koşar —
|
|
2823
|
+
chat Beast'i Finance'tan paralel finance ajanı açabilir */
|
|
2824
|
+
const parentSess = parent ? this.cache.get(parent) || null : null;
|
|
2825
|
+
if (parentSess && parentSess.finance) {
|
|
2826
|
+
bg.finance = true;
|
|
2827
|
+
bg.financeTrader = false; /* işçi — asla otonom trade, yalnız analiz/rapor */
|
|
2828
|
+
bg.financeAuto = false;
|
|
2829
|
+
}
|
|
2791
2830
|
const ttl = String(title || t).replace(/\s+/g, ' ').trim().slice(0, 80) || 'arka plan görevi';
|
|
2792
2831
|
/* #17 cache'teki nesneye de işaret koy — listSessions temiz başlık göstersin */
|
|
2793
2832
|
bg.bgTitle = ttl;
|
|
@@ -3042,10 +3081,19 @@ class Engine {
|
|
|
3042
3081
|
return { ok: true, job, messages: tail.slice(-30) };
|
|
3043
3082
|
}
|
|
3044
3083
|
|
|
3045
|
-
/* İş bitişi kaydı: status=done|error|aborted. Hata olursa üst sohbete haber gider.
|
|
3084
|
+
/* İş bitişi kaydı: status=done|error|aborted. Hata olursa üst sohbete haber gider.
|
|
3085
|
+
SÜREKLİ (continuous) işler — Finance trade ajanları gibi tur tur koşanlar —
|
|
3086
|
+
'done'/'error'da KAPANMAZ: tur sonu = aktivite tazelenir, iş koşmaya devam
|
|
3087
|
+
eder (döngüyü main yönetir). Yalnız 'aborted' (kullanıcı iptali) gerçek bitiştir. */
|
|
3046
3088
|
_bgFinish(sid, status, errorMsg) {
|
|
3047
3089
|
const job = this._bgJobs.get(String(sid));
|
|
3048
3090
|
if (!job || job.status !== 'running') return;
|
|
3091
|
+
if (job.continuous && (status === 'done' || status === 'error')) {
|
|
3092
|
+
job.lastActivityAt = nowIso();
|
|
3093
|
+
if (status === 'error') job.errors = (job.errors || 0) + 1;
|
|
3094
|
+
this._bgEmit();
|
|
3095
|
+
return;
|
|
3096
|
+
}
|
|
3049
3097
|
job.status = status;
|
|
3050
3098
|
job.revive = false;
|
|
3051
3099
|
job.endedAt = nowIso();
|
|
@@ -3113,9 +3161,11 @@ class Engine {
|
|
|
3113
3161
|
job.lastActivityAt = nowIso();
|
|
3114
3162
|
}
|
|
3115
3163
|
|
|
3116
|
-
/* Saf sınıflandırma (test edilebilir): null | 'stuck' | 'long'
|
|
3164
|
+
/* Saf sınıflandırma (test edilebilir): null | 'stuck' | 'long'
|
|
3165
|
+
continuous işler (Finance trade ajanları) denetlenmez — turlar arası
|
|
3166
|
+
boşluk normaldir; döngüyü main yönetir. */
|
|
3117
3167
|
static superviseReason(job, nowMs) {
|
|
3118
|
-
if (!job || job.status !== 'running') return null;
|
|
3168
|
+
if (!job || job.status !== 'running' || job.continuous) return null;
|
|
3119
3169
|
const started = Date.parse(job.startedAt || '') || nowMs;
|
|
3120
3170
|
const lastAct = Date.parse(job.lastActivityAt || '') || started;
|
|
3121
3171
|
const runMin = (nowMs - started) / 60000;
|
|
@@ -3356,6 +3406,7 @@ class Engine {
|
|
|
3356
3406
|
`- Bağımsız araç çağrılarını aynı turda PARALEL ver.\n` +
|
|
3357
3407
|
`- PDF gerekirse pip\u2019ten paket kurma (Türkçe bozar); Node\u2019un kurulu \`pdf-lib\`+fontkit\u2019iyle .js script yazıp \`node\` ile çalıştır. md→pdf çevirme: belge çıktısını doğrudan PDF olarak üret.\n` +
|
|
3358
3408
|
`- ARAŞTIRMA SINIRI: 3-5 kaynak yeter; süre hedefi ~3 dakika. 2-3 denemede bulunamayan bilgiyi BIRAK — bulabildiğin kısmi sonucu raporla ve neyi bulamadığını yaz. Kapalı/gizli içerik peşinde koşma.\n` +
|
|
3409
|
+
`AJAN KOORDİNASYONU: diğer koşan ajanlarla (paralel işler, finance ajanları) konuşman gerekiyorsa agent_dm aracını kullan — to: hedefin başlığındaki anahtar kelime (örn "GOLD", "Trader"), message: 1-3 cümlelik net mesaj. Senden istenen görevde başka bir ajanın zaten yaptığın işe ihtiyacı varsa DM ile haber ver.\n` +
|
|
3359
3410
|
FORMAT_RULES + '\n' +
|
|
3360
3411
|
`SON ÇIKTI: 3-5 satırlık net sonuç raporu, madde madde. Soru sorma, sohbet etme.`
|
|
3361
3412
|
);
|
|
@@ -3402,6 +3453,21 @@ class Engine {
|
|
|
3402
3453
|
toolsList = await mcp.mergeTools(toolsList);
|
|
3403
3454
|
/* Beast Apps: kurulu app'lerin araçları (app__<id>__<tool>) modele açılır */
|
|
3404
3455
|
toolsList = apps.mergeTools(toolsList);
|
|
3456
|
+
/* Beast Finance: skill aracı da açık — kataloğun SKILL.md gövdesi
|
|
3457
|
+
modele açılır (skill handler'ı zaten tüm oturumlar için çalışır,
|
|
3458
|
+
definition yalnız BC'de vardı; finance chat copilot + trader ikisi de)
|
|
3459
|
+
Not: BC oturumları zaten opencode registry'sinden skill'i görür. */
|
|
3460
|
+
if (session && session.finance) {
|
|
3461
|
+
const skillDef = (opencode.toolmap.definitions() || []).find(
|
|
3462
|
+
(d) => d && d.function && d.function.name === 'skill'
|
|
3463
|
+
);
|
|
3464
|
+
if (skillDef) toolsList = [...toolsList, skillDef];
|
|
3465
|
+
}
|
|
3466
|
+
/* AJAN DM: koşan ajan oturumlarına (arka plan işleri + finance
|
|
3467
|
+
ajanları) ajanlar-arası mesajlaşma aracı verilir */
|
|
3468
|
+
if (session && (session.bgJob || (session.finance && session.bgTitle))) {
|
|
3469
|
+
toolsList = [...toolsList, AGENT_DM_DEF];
|
|
3470
|
+
}
|
|
3405
3471
|
/* panel_run yalnız SANDBOX oturumlarında görünsün — diğer panellerde
|
|
3406
3472
|
modelin alet çantasında olmasın (hook olmadan çalışmaz) */
|
|
3407
3473
|
if (!(session && session.sbSandbox)) {
|
|
@@ -3411,12 +3477,12 @@ class Engine {
|
|
|
3411
3477
|
/* opencode agent.ts port: özel ajan tanımı — prompt/model/araç/steps */
|
|
3412
3478
|
const adef = this._agentDefFor(session, !!session.bgJob);
|
|
3413
3479
|
const promptText = this._lastUserText(session);
|
|
3414
|
-
let system = session.
|
|
3415
|
-
? this.
|
|
3416
|
-
: session.
|
|
3417
|
-
? this.
|
|
3418
|
-
: session.
|
|
3419
|
-
? this.
|
|
3480
|
+
let system = session.finance
|
|
3481
|
+
? this.buildFinanceSystem(session) /* finance önceliklidir — bg finance işçileri de finance promptuyla koşar */
|
|
3482
|
+
: session.bgJob
|
|
3483
|
+
? this.buildBgSystem(session)
|
|
3484
|
+
: session.studio
|
|
3485
|
+
? this.buildStudioSystem(session)
|
|
3420
3486
|
: bcAgent
|
|
3421
3487
|
? this.buildOcSystem(session, bcAgent)
|
|
3422
3488
|
: await this.buildSystem(promptText, session);
|
|
@@ -4653,6 +4719,26 @@ const skills = require('./skills');
|
|
|
4653
4719
|
return null;
|
|
4654
4720
|
}
|
|
4655
4721
|
|
|
4722
|
+
/* opencode skill aracı gövdesi: katalogdan eşleşen SKILL.md'yi okur.
|
|
4723
|
+
Salt-okur — tüm oturum türlerinde (BC/finance/chat/trader) aynı yol. */
|
|
4724
|
+
_skillBody(args) {
|
|
4725
|
+
const wanted = String((args && args.name) || '').trim();
|
|
4726
|
+
let list = [];
|
|
4727
|
+
try { list = skills.scan(); } catch {}
|
|
4728
|
+
const hit = list.find((s) => s && s.name === wanted) || null;
|
|
4729
|
+
if (!hit) {
|
|
4730
|
+
return {
|
|
4731
|
+
ok: false,
|
|
4732
|
+
error:
|
|
4733
|
+
'skill bulunamadı: ' + (wanted || '(boş)') +
|
|
4734
|
+
(list.length ? ' — mevcut: ' + list.map((s) => s.name).join(', ') : ''),
|
|
4735
|
+
};
|
|
4736
|
+
}
|
|
4737
|
+
let body = '';
|
|
4738
|
+
try { body = fs.readFileSync(hit.path, 'utf8'); } catch {}
|
|
4739
|
+
return { ok: true, name: hit.name, path: hit.path, content: body.slice(0, 24000) };
|
|
4740
|
+
}
|
|
4741
|
+
|
|
4656
4742
|
async _execTool(name, args, signal, sessionId) {
|
|
4657
4743
|
try {
|
|
4658
4744
|
/* opencode mantığı (BC): model opencode builtin dilinde çağırır —
|
|
@@ -4669,6 +4755,16 @@ const skills = require('./skills');
|
|
|
4669
4755
|
/* ANA KOD KİLİDİ: korumalı bölgeye yazma/silme girişimi daha kapıdan geçmez */
|
|
4670
4756
|
const blocked = this._guardTool(name, args);
|
|
4671
4757
|
if (blocked) return blocked;
|
|
4758
|
+
/* opencode skill aracı: katalogdaki SKILL.md gövdesini modele açar.
|
|
4759
|
+
Salt-okur ve güvenli — BC dahil TÜM oturum türlerinde çalışır
|
|
4760
|
+
(Beast Finance chat copilot + trader buradan kullanır). */
|
|
4761
|
+
if (name === 'skill') {
|
|
4762
|
+
return JSON.stringify(this._skillBody(args));
|
|
4763
|
+
}
|
|
4764
|
+
/* AJAN DM: ajanlar arası mesaj — hedef koşan ajanın oturumuna düşer */
|
|
4765
|
+
if (name === 'agent_dm') {
|
|
4766
|
+
return JSON.stringify(this._agentDmSend(sessionId, args));
|
|
4767
|
+
}
|
|
4672
4768
|
if (isBc) {
|
|
4673
4769
|
/* opencode permission akışı (tool/external-directory.ts + her aracın
|
|
4674
4770
|
ctx.ask'ı): önce workspace DIŞI erişim izni, sonra araç izni.
|
|
@@ -4695,24 +4791,6 @@ const skills = require('./skills');
|
|
|
4695
4791
|
} catch (permErr) {
|
|
4696
4792
|
return JSON.stringify({ ok: false, error: AskService.errorText(permErr) });
|
|
4697
4793
|
}
|
|
4698
|
-
/* opencode skill aracı: katalogdaki SKILL.md gövdesini modele açar */
|
|
4699
|
-
if (name === 'skill') {
|
|
4700
|
-
const wanted = String((args && args.name) || '').trim();
|
|
4701
|
-
let list = [];
|
|
4702
|
-
try { list = skills.scan(); } catch {}
|
|
4703
|
-
const hit = list.find((s) => s && s.name === wanted) || null;
|
|
4704
|
-
if (!hit) {
|
|
4705
|
-
return JSON.stringify({
|
|
4706
|
-
ok: false,
|
|
4707
|
-
error:
|
|
4708
|
-
'skill bulunamadı: ' + (wanted || '(boş)') +
|
|
4709
|
-
(list.length ? ' — mevcut: ' + list.map((s) => s.name).join(', ') : ''),
|
|
4710
|
-
});
|
|
4711
|
-
}
|
|
4712
|
-
let body = '';
|
|
4713
|
-
try { body = fs.readFileSync(hit.path, 'utf8'); } catch {}
|
|
4714
|
-
return JSON.stringify({ ok: true, name: hit.name, path: hit.path, content: body.slice(0, 24000) });
|
|
4715
|
-
}
|
|
4716
4794
|
} else {
|
|
4717
4795
|
/* onay kapısı: riskli araçta dış onay bekle; reddedilirse araç çalışmaz.
|
|
4718
4796
|
"always" onaylı araçlar doğrudan geçer. (BC dışı oturumlar — opencode
|
|
@@ -5208,6 +5286,26 @@ function emitSafe(engine, sessionId, ev) {
|
|
|
5208
5286
|
engine.emit({ ...ev, sessionId });
|
|
5209
5287
|
}
|
|
5210
5288
|
|
|
5289
|
+
/* AJAN DM aracı: paralel/finance ajanlarının birbirine kısa mesaj atması.
|
|
5290
|
+
Salt metin — hedef ajanın oturumuna [AJAN DM] bloğu olarak düşer; AJAN DM
|
|
5291
|
+
panelinde iki yönlü görünür. */
|
|
5292
|
+
const AGENT_DM_DEF = {
|
|
5293
|
+
type: 'function',
|
|
5294
|
+
function: {
|
|
5295
|
+
name: 'agent_dm',
|
|
5296
|
+
description:
|
|
5297
|
+
'Send a short DM to another RUNNING agent (parallel agents / finance agents) to coordinate: share findings, ask status, warn about risk, hand off work. `to` = target session id OR a keyword from the agent title (e.g. "GOLD", "Trader"). The message is delivered to the other agent and shown in the Agent DM panel.',
|
|
5298
|
+
parameters: {
|
|
5299
|
+
type: 'object',
|
|
5300
|
+
properties: {
|
|
5301
|
+
to: { type: 'string', description: 'Target agent session id or a title keyword' },
|
|
5302
|
+
message: { type: 'string', description: 'Short message (1-3 sentences)' },
|
|
5303
|
+
},
|
|
5304
|
+
required: ['to', 'message'],
|
|
5305
|
+
},
|
|
5306
|
+
},
|
|
5307
|
+
};
|
|
5308
|
+
|
|
5211
5309
|
const TOOLS = [
|
|
5212
5310
|
...tools.definitions,
|
|
5213
5311
|
{
|
|
@@ -5969,6 +6067,99 @@ Engine.prototype.undoLastTodo = function (sid) {
|
|
|
5969
6067
|
return this.undoTodo(sid, j[j.length - 1].todoId);
|
|
5970
6068
|
};
|
|
5971
6069
|
|
|
6070
|
+
/* /deltodo: oturumun todo listesini tamamen temizler. Dosyaya BOŞ t:todo
|
|
6071
|
+
kaydı append edilir — yüklemede son kayıt kazanır, restart sonrası da
|
|
6072
|
+
boş kalır (eski t:todo satırları yeniden canlanamaz). */
|
|
6073
|
+
Engine.prototype.clearTodos = function (sid) {
|
|
6074
|
+
const key = String(sid || '');
|
|
6075
|
+
try {
|
|
6076
|
+
if (!key || !fs.existsSync(this._file(key))) return { ok: false, error: 'oturum bulunamadı' };
|
|
6077
|
+
} catch {
|
|
6078
|
+
return { ok: false, error: 'oturum bulunamadı' };
|
|
6079
|
+
}
|
|
6080
|
+
const prev = this.todos.get(key) || [];
|
|
6081
|
+
this.todos.set(key, []);
|
|
6082
|
+
try {
|
|
6083
|
+
fs.appendFileSync(this._file(key), JSON.stringify({ t: 'todo', items: [] }) + '\n');
|
|
6084
|
+
} catch {}
|
|
6085
|
+
emitSafe(this, key, { type: 'todos', sessionId: key, todos: [] });
|
|
6086
|
+
return { ok: true, count: prev.length };
|
|
6087
|
+
};
|
|
6088
|
+
|
|
6089
|
+
/* ---------- AJAN DM: ajanların birbirine mesajı ---------- */
|
|
6090
|
+
|
|
6091
|
+
/* Ajan DM gönder: hedef koşan paralel/finance ajanı (id YA DA başlık
|
|
6092
|
+
anahtar kelimesi). Teslim _pendingReports kuyruğuyla yapılır — hedef
|
|
6093
|
+
meşgulse mesaj done olunca düşer, kaybolmaz. Panel agent-dms.json'dan. */
|
|
6094
|
+
Engine.prototype._agentDmSend = function (fromSid, args) {
|
|
6095
|
+
try {
|
|
6096
|
+
const to = String((args && args.to) || '').trim();
|
|
6097
|
+
const text = String((args && args.message) || '').trim().slice(0, 1200);
|
|
6098
|
+
if (!text) return { ok: false, error: 'mesaj boş' };
|
|
6099
|
+
const jobs = this._bgJobs || new Map();
|
|
6100
|
+
let target = jobs.has(to) ? to : '';
|
|
6101
|
+
if (!target) {
|
|
6102
|
+
const q = to.toLowerCase();
|
|
6103
|
+
for (const [sid, j] of jobs) {
|
|
6104
|
+
if (sid === String(fromSid)) continue;
|
|
6105
|
+
if (j && j.title && String(j.title).toLowerCase().includes(q)) {
|
|
6106
|
+
target = sid;
|
|
6107
|
+
break;
|
|
6108
|
+
}
|
|
6109
|
+
}
|
|
6110
|
+
}
|
|
6111
|
+
if (!target || !jobs.has(target)) {
|
|
6112
|
+
const titles = [...jobs.values()]
|
|
6113
|
+
.filter((j) => j && j.status === 'running' && j.title)
|
|
6114
|
+
.map((j) => j.title)
|
|
6115
|
+
.slice(0, 8);
|
|
6116
|
+
return {
|
|
6117
|
+
ok: false,
|
|
6118
|
+
error: 'hedef ajan bulunamadı: "' + to + '"' + (titles.length ? ' — koşan ajanlar: ' + titles.join(', ') : ''),
|
|
6119
|
+
};
|
|
6120
|
+
}
|
|
6121
|
+
if (String(target) === String(fromSid)) return { ok: false, error: 'kendine DM atılamaz' };
|
|
6122
|
+
const fromJob = jobs.get(String(fromSid));
|
|
6123
|
+
const toJob = jobs.get(target);
|
|
6124
|
+
const dm = {
|
|
6125
|
+
at: new Date().toISOString(),
|
|
6126
|
+
from: String(fromSid),
|
|
6127
|
+
fromTitle: (fromJob && fromJob.title) || 'Ajan',
|
|
6128
|
+
to: String(target),
|
|
6129
|
+
toTitle: (toJob && toJob.title) || 'Ajan',
|
|
6130
|
+
text,
|
|
6131
|
+
};
|
|
6132
|
+
this._agentDms = this._agentDms || [];
|
|
6133
|
+
this._agentDms.push(dm);
|
|
6134
|
+
if (this._agentDms.length > 400) this._agentDms.splice(0, this._agentDms.length - 400);
|
|
6135
|
+
try {
|
|
6136
|
+
fs.writeFileSync(this._agentDmsFile, JSON.stringify({ dms: this._agentDms }, null, 2));
|
|
6137
|
+
} catch {}
|
|
6138
|
+
emitSafe(this, target, { type: 'agent-dm', ...dm });
|
|
6139
|
+
/* teslim: hedef meşgulse pending kuyruğunda bekler, done olunca düşer */
|
|
6140
|
+
(this._pendingReports = this._pendingReports || []).push({
|
|
6141
|
+
parentId: target,
|
|
6142
|
+
text:
|
|
6143
|
+
`[AJAN DM — ${dm.fromTitle}]\n${text}\n` +
|
|
6144
|
+
`(Cevabını agent_dm aracıyla ver — to: "${dm.fromTitle}")`,
|
|
6145
|
+
});
|
|
6146
|
+
this.flushPendingReports(target);
|
|
6147
|
+
return { ok: true, to: target, toTitle: dm.toTitle };
|
|
6148
|
+
} catch (e) {
|
|
6149
|
+
return { ok: false, error: String((e && e.message) || e) };
|
|
6150
|
+
}
|
|
6151
|
+
};
|
|
6152
|
+
|
|
6153
|
+
Engine.prototype.agentDmsList = function () {
|
|
6154
|
+
return [...(this._agentDms || [])].slice(-200);
|
|
6155
|
+
};
|
|
6156
|
+
|
|
6157
|
+
Engine.prototype.agentDmsClear = function () {
|
|
6158
|
+
this._agentDms = [];
|
|
6159
|
+
try { fs.rmSync(this._agentDmsFile, { force: true }); } catch {}
|
|
6160
|
+
return { ok: true };
|
|
6161
|
+
};
|
|
6162
|
+
|
|
5972
6163
|
module.exports = Engine;
|
|
5973
6164
|
module.exports.Engine = Engine;
|
|
5974
6165
|
module.exports.sanitizeTodoItems = sanitizeTodoItems;
|
package/src/agent/skills.js
CHANGED
|
@@ -137,7 +137,7 @@ Get-Content "$env:APPDATA\\beast\\sessions\\bg-jobs.json" | ConvertFrom-Json | %
|
|
|
137
137
|
| \`bin/beast-agent.js\` | CLI: \`beast\` (başlat), \`beast update\`, \`beast uninstall\` |
|
|
138
138
|
|
|
139
139
|
## SLASH KOMUTLARI
|
|
140
|
-
/help · /version · /new · /open <kod> · /sessions · /stop · /start · /restart · /change [n] · /model <isim> · /think 0-5 · /clear · /notes · /rule <metin> · /rules · /notify on|off · /screenshot · /approve [always] · /deny · /update [now] · /usage · /backup · /status · /skills
|
|
140
|
+
/help · /version · /new · /open <kod> · /sessions · /stop · /start · /restart · /change [n] · /model <isim> · /think 0-5 · /clear · /autodel [all] (hatırlatmaları sil) · /deltodo [all] (todoları sil) · /cron list|clear|del <id> (WA) · /notes · /rule <metin> · /rules · /notify on|off · /screenshot · /approve [always] · /deny · /update [now] · /usage · /backup · /status · /skills
|
|
141
141
|
|
|
142
142
|
## AYAR SEKMELERİ
|
|
143
143
|
Provider · Fallout · Skills · Paralel Ajanlar · TTS · E-posta · Entegrasyonlar · Web Arama · MCP · Olaylar · Cron · Maliyet · Loglar · Panel · Limitler · Güvenlik · Güncelleme
|
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 };
|