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/src/main.js
CHANGED
|
@@ -1106,6 +1106,8 @@ function waSlashHelp() {
|
|
|
1106
1106
|
'• */usage* – bugünkü kullanım',
|
|
1107
1107
|
'• */backup* – tüm veriyi ŞİFRELİ yedekle (Beast Kodu imzalı, Masaüstü\\Beast-Backups)',
|
|
1108
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ı',
|
|
1109
1111
|
'',
|
|
1110
1112
|
'Gruplarda beni @mention ile çağır; ardışık mesajlarını tek cevapta birleştiririm.',
|
|
1111
1113
|
].join('\n');
|
|
@@ -1646,6 +1648,58 @@ async function tryWaSlash(jid, rawText, senderNum, payload0) {
|
|
|
1646
1648
|
} else {
|
|
1647
1649
|
out = 'Kullanım: `/cron list` · `/cron clear` (hepsini sil) · `/cron del <id>`';
|
|
1648
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
|
+
}
|
|
1649
1703
|
} else if (cmd === 'beastcode') {
|
|
1650
1704
|
/* /beastcode [görev] — WhatsApp'tan UZAKTAN KODLAMA modu:
|
|
1651
1705
|
masaüstünde GERÇEK Beast Code paneli açılır (IDE ekranı), sohbet
|
|
@@ -2084,6 +2138,22 @@ async function documentToTextAsync(media) {
|
|
|
2084
2138
|
|
|
2085
2139
|
/* ---------- WA hatırlatıcı ---------- */
|
|
2086
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
|
+
|
|
2087
2157
|
function scheduleReminder({ when, message, sessionId, repeat }) {
|
|
2088
2158
|
try {
|
|
2089
2159
|
const msg = String(message || '').trim();
|
|
@@ -2097,6 +2167,7 @@ function scheduleReminder({ when, message, sessionId, repeat }) {
|
|
|
2097
2167
|
name: 'Tekrarlı hatırlatma: ' + msg.slice(0, 40),
|
|
2098
2168
|
schedule: s.schedule,
|
|
2099
2169
|
prompt: `[TEKRARLI HATIRLATMA] Kullanıcıya şunu hatırlat: "${msg}". Kısaca ve nazikçe bildir.`,
|
|
2170
|
+
kind: 'reminder',
|
|
2100
2171
|
sessionId: sessionId || undefined,
|
|
2101
2172
|
});
|
|
2102
2173
|
if (!r.ok) return r;
|
|
@@ -2111,6 +2182,7 @@ function scheduleReminder({ when, message, sessionId, repeat }) {
|
|
|
2111
2182
|
name: 'Hatırlatma: ' + msg.slice(0, 40),
|
|
2112
2183
|
schedule: cronExpr,
|
|
2113
2184
|
prompt: `[HATIRLATMA ZAMANI] Kullanıcıya şunu hatırlat: "${msg}". Kısaca ve nazikçe bildir.`,
|
|
2185
|
+
kind: 'reminder',
|
|
2114
2186
|
once: true,
|
|
2115
2187
|
sessionId: sessionId || undefined,
|
|
2116
2188
|
});
|
|
@@ -2291,6 +2363,23 @@ async function handleWaIncoming(jid, payload, senderNum) {
|
|
|
2291
2363
|
return;
|
|
2292
2364
|
}
|
|
2293
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
|
+
|
|
2294
2383
|
if (isGroup) {
|
|
2295
2384
|
const g = settings.waGroups || {};
|
|
2296
2385
|
if (!g.enabled) return;
|
|
@@ -3307,11 +3396,11 @@ function reloadBackend() {
|
|
|
3307
3396
|
}
|
|
3308
3397
|
/* CRON → TÜM AKTİF ENTEGRASYONLAR: cron işi bittiğinde cevap (ya da
|
|
3309
3398
|
hata) sahibin bağlı olduğu her kanala yansıtılır — WA + Telegram +
|
|
3310
|
-
Discord. Aynı kanal hem cron oturumuna bağlıysa TEK cevap alır.
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
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) {
|
|
3315
3404
|
(async () => {
|
|
3316
3405
|
try {
|
|
3317
3406
|
let txt = '';
|
|
@@ -5011,6 +5100,57 @@ ipcMain.handle('agent:send', (_e, { sessionId, text }) => {
|
|
|
5011
5100
|
desktopEcho(sessionId, '/help', desktopSlashHelp());
|
|
5012
5101
|
return true;
|
|
5013
5102
|
}
|
|
5103
|
+
if (t === '/autodel' || t.startsWith('/autodel ')) {
|
|
5104
|
+
/* /autodel [all] — otomatik hatırlatmaları tek komutla sil (WA ile aynı davranış) */
|
|
5105
|
+
const arg = t.slice(8).trim().toLowerCase();
|
|
5106
|
+
if (arg === 'all' || arg === 'hepsi' || arg === 'hepsini') {
|
|
5107
|
+
const r = cron.clearAll();
|
|
5108
|
+
cronEmit();
|
|
5109
|
+
desktopEcho(sessionId, t, r.ok ? `**Tüm zamanlanmış görevler silindi** (${r.count} adet).` : 'Temizlenemedi.');
|
|
5110
|
+
} else {
|
|
5111
|
+
const r = cron.removeIf(isReminderJob);
|
|
5112
|
+
cronEmit();
|
|
5113
|
+
const left = cron.list().filter((j) => j.enabled).length;
|
|
5114
|
+
desktopEcho(
|
|
5115
|
+
sessionId,
|
|
5116
|
+
t,
|
|
5117
|
+
r.count
|
|
5118
|
+
? `**${r.count} otomatik hatırlatma silindi.** Kalan aktif görev: ${left}`
|
|
5119
|
+
: 'Silinecek otomatik hatırlatma yok — zaten temiz.'
|
|
5120
|
+
);
|
|
5121
|
+
}
|
|
5122
|
+
return true;
|
|
5123
|
+
}
|
|
5124
|
+
if (t === '/deltodo' || t.startsWith('/deltodo ')) {
|
|
5125
|
+
/* /deltodo [all] — todo listelerini temizle (WA ile aynı davranış) */
|
|
5126
|
+
const arg = t.slice(8).trim().toLowerCase();
|
|
5127
|
+
if (arg === 'all' || arg === 'hepsi' || arg === 'hepsini') {
|
|
5128
|
+
let n = 0;
|
|
5129
|
+
let items = 0;
|
|
5130
|
+
try {
|
|
5131
|
+
for (const v of engine.listSessions()) {
|
|
5132
|
+
const r = engine.clearTodos(v.id);
|
|
5133
|
+
if (r && r.ok) {
|
|
5134
|
+
n++;
|
|
5135
|
+
items += r.count || 0;
|
|
5136
|
+
}
|
|
5137
|
+
}
|
|
5138
|
+
} catch {}
|
|
5139
|
+
desktopEcho(sessionId, t, n ? `**Tüm oturumların todoları temizlendi** (${n} oturum, ${items} madde).` : 'Temizlenecek todo yok.');
|
|
5140
|
+
} else {
|
|
5141
|
+
const r = engine.clearTodos(String(sessionId || ''));
|
|
5142
|
+
desktopEcho(
|
|
5143
|
+
sessionId,
|
|
5144
|
+
t,
|
|
5145
|
+
r.ok
|
|
5146
|
+
? r.count
|
|
5147
|
+
? `**${r.count} todo temizlendi.** (tüm oturumlar: /deltodo all)`
|
|
5148
|
+
: 'Todo listesi zaten boş. (tüm oturumlar: /deltodo all)'
|
|
5149
|
+
: 'Oturum bulunamadı — todo listesi yok.'
|
|
5150
|
+
);
|
|
5151
|
+
}
|
|
5152
|
+
return true;
|
|
5153
|
+
}
|
|
5014
5154
|
if (t === '/rules') {
|
|
5015
5155
|
const rs = memory.listRules();
|
|
5016
5156
|
desktopEcho(sessionId, t, rs.length ? '**Kalıcı kurallar:**\n' + rs.map((r0, i) => `${i + 1}. ${r0}`).join('\n') : 'Kalıcı kural yok — ekle: **/rule <metin>**');
|
|
@@ -5205,6 +5345,8 @@ function desktopSlashHelp() {
|
|
|
5205
5345
|
'**/think 0-5** – düşünme seviyesi (0 kapalı · 5 max)',
|
|
5206
5346
|
'**/agent [isim]** – özel ajan bağla / listele (%APPDATA%\\beast\\agents\\*.md)',
|
|
5207
5347
|
'**/clear** – oturum geçmişini gerçekten sil (kod korunur)',
|
|
5348
|
+
'**/autodel** – tüm otomatik hatırlatmaları tek komutla sil · **/autodel all** – cron görevleri dahil hepsi',
|
|
5349
|
+
'**/deltodo** – bu oturumun todo listesini temizle · **/deltodo all** – tüm oturumların todoları',
|
|
5208
5350
|
'**/notes** – bu oturumun notlarını göster',
|
|
5209
5351
|
'**/rule <metin>** – kalıcı kural ekle · **/rules** – listele',
|
|
5210
5352
|
'**/notify on|off** – hata mail bildirimini aç/kapa',
|
|
@@ -5738,9 +5880,18 @@ ipcMain.handle('memory:get', () => memory.loadAll());
|
|
|
5738
5880
|
/* paralel ajanlar: canlı izleme (#14) */
|
|
5739
5881
|
ipcMain.handle('agents:list', () => engine.listBgJobs());
|
|
5740
5882
|
ipcMain.handle('agents:detail', (_e, id) => engine.bgDetail(id));
|
|
5741
|
-
ipcMain.handle('agents:cancel', (_e, id) =>
|
|
5742
|
-
|
|
5743
|
-
|
|
5883
|
+
ipcMain.handle('agents:cancel', (_e, id) => {
|
|
5884
|
+
const sid = String(id || '');
|
|
5885
|
+
/* Finance ajanı (sürekli iş): interrupt yetmez — döngüyü de kapat */
|
|
5886
|
+
if (financeState && financeState.agents && financeState.agents.has(sid)) {
|
|
5887
|
+
finAgentStop(sid, 'kullanıcı Paralel Ajanlar panelinden (×) iptal etti');
|
|
5888
|
+
return { ok: true };
|
|
5889
|
+
}
|
|
5890
|
+
return { ok: !!engine.interrupt(sid, 'kullanıcı Paralel Ajanlar panelinden (×) iptal etti') };
|
|
5891
|
+
});
|
|
5892
|
+
/* AJAN DM paneli: ajanlar arası mesaj arşivi */
|
|
5893
|
+
ipcMain.handle('agent-dms:list', () => (engine ? engine.agentDmsList() : []));
|
|
5894
|
+
ipcMain.handle('agent-dms:clear', () => (engine ? engine.agentDmsClear() : { ok: false }));
|
|
5744
5895
|
ipcMain.handle('ceo:get', () => !!engine.ceoMode);
|
|
5745
5896
|
ipcMain.handle('ceo:set', (_e, v) => {
|
|
5746
5897
|
settings.ceoMode = !!v;
|
|
@@ -6519,8 +6670,40 @@ function cronEmit() {
|
|
|
6519
6670
|
|
|
6520
6671
|
/* Cron cevabı bekleme haritası: sid -> job. İş 'done' olunca cevap
|
|
6521
6672
|
SAHİBİN bağlı olduğu TÜM AKTİF entegrasyonlara yansıtılır (kullanıcı
|
|
6522
|
-
hangi kanaldan ajanla iletişimde belli değil).
|
|
6673
|
+
hangi kanaldan ajanla iletişimde belli değil). Kayıtlar TTL'li —
|
|
6674
|
+
done/error hiç gelmezse bayat bekleme sonraki done'a yansıtılmaz. */
|
|
6523
6675
|
const cronAnswerPending = new Map();
|
|
6676
|
+
const cronAnswerPendingAt = new Map();
|
|
6677
|
+
const CRON_ANSWER_TTL_MS = 30 * 60 * 1000;
|
|
6678
|
+
|
|
6679
|
+
function cronAnswerPendingSet(sid, job) {
|
|
6680
|
+
cronAnswerPendingSweep();
|
|
6681
|
+
cronAnswerPending.set(String(sid), job);
|
|
6682
|
+
cronAnswerPendingAt.set(String(sid), Date.now());
|
|
6683
|
+
}
|
|
6684
|
+
|
|
6685
|
+
function cronAnswerPendingDrop(sid) {
|
|
6686
|
+
cronAnswerPending.delete(String(sid));
|
|
6687
|
+
cronAnswerPendingAt.delete(String(sid));
|
|
6688
|
+
}
|
|
6689
|
+
|
|
6690
|
+
/* Bayat kayıt (TTL aşımı) haritalardan temizlenir */
|
|
6691
|
+
function cronAnswerPendingSweep() {
|
|
6692
|
+
const now = Date.now();
|
|
6693
|
+
for (const [k, at] of cronAnswerPendingAt) {
|
|
6694
|
+
if (now - at > CRON_ANSWER_TTL_MS) cronAnswerPendingDrop(k);
|
|
6695
|
+
}
|
|
6696
|
+
}
|
|
6697
|
+
|
|
6698
|
+
/* Kaydı okuyup düşürür; bayat kayıt ise null döner (yansıtma yok) */
|
|
6699
|
+
function cronAnswerPendingTake(sid) {
|
|
6700
|
+
const k = String(sid);
|
|
6701
|
+
const job = cronAnswerPending.get(k);
|
|
6702
|
+
const at = cronAnswerPendingAt.get(k) || 0;
|
|
6703
|
+
cronAnswerPendingDrop(k);
|
|
6704
|
+
if (!job || Date.now() - at > CRON_ANSWER_TTL_MS) return null;
|
|
6705
|
+
return job;
|
|
6706
|
+
}
|
|
6524
6707
|
|
|
6525
6708
|
/* OTOMATİK YENİ SOHBET YOK: cron/izleyici/fallout tetiklendiğinde oturum
|
|
6526
6709
|
seçimi — kayıtlı id'nin DOSYASI hâlâ duruyorsa O, değilse EN GÜNCEL
|
|
@@ -6600,11 +6783,11 @@ function cronFire(job) {
|
|
|
6600
6783
|
if (sid !== String(job.sessionId || '')) {
|
|
6601
6784
|
cron.update(job.id, { sessionId: sid });
|
|
6602
6785
|
}
|
|
6603
|
-
|
|
6786
|
+
cronAnswerPendingSet(sid, job);
|
|
6604
6787
|
const sent = engine.send(sid, {
|
|
6605
6788
|
text: `[cron: ${job.name}]\n${job.prompt}`,
|
|
6606
6789
|
});
|
|
6607
|
-
if (!sent)
|
|
6790
|
+
if (!sent) cronAnswerPendingDrop(sid); // gönderilemedi — bayat bekleme bırakma
|
|
6608
6791
|
} catch {}
|
|
6609
6792
|
cronEmit();
|
|
6610
6793
|
}
|
|
@@ -7124,6 +7307,9 @@ ipcMain.handle('watchers:toggle', (_e, id) => {
|
|
|
7124
7307
|
watchers.patch(String(id), { enabled: !w.enabled });
|
|
7125
7308
|
return { ok: true, watcher: watchers.get(String(id)) };
|
|
7126
7309
|
});
|
|
7310
|
+
/* izleyici logları — ana depodan ayrı watcher-logs.json üzerinden */
|
|
7311
|
+
ipcMain.handle('watchers:logs', () => watchers.logsAll());
|
|
7312
|
+
ipcMain.handle('watchers:logsClear', (_e, id) => watchers.logClear(id ? String(id) : null));
|
|
7127
7313
|
ipcMain.handle('cron:add', (_e, job) => {
|
|
7128
7314
|
const r = cron.add(job || {});
|
|
7129
7315
|
cronEmit();
|
|
@@ -8090,13 +8276,13 @@ const financetools = require('./agent/financetools');
|
|
|
8090
8276
|
|
|
8091
8277
|
const financeState = {
|
|
8092
8278
|
mode: false, /* chat oturumları finance bayrağı alıyor mu */
|
|
8093
|
-
traderSid: null,
|
|
8279
|
+
traderSid: null, /* ANA trader (panel kartı buna bağlı) */
|
|
8094
8280
|
traderOn: false,
|
|
8095
|
-
traderTimer: null,
|
|
8096
8281
|
traderRounds: 0,
|
|
8097
8282
|
lastRoundAt: 0,
|
|
8098
8283
|
installing: false,
|
|
8099
8284
|
installTried: false,
|
|
8285
|
+
agents: new Map(), /* sid -> { symbols, main, round, timer } — AYNI ANDA koşan finance ajanları */
|
|
8100
8286
|
};
|
|
8101
8287
|
|
|
8102
8288
|
function financeDir() {
|
|
@@ -8108,7 +8294,10 @@ function financeDir() {
|
|
|
8108
8294
|
function finCfg() {
|
|
8109
8295
|
if (!settings.finance || typeof settings.finance !== 'object') settings.finance = {};
|
|
8110
8296
|
const f = settings.finance;
|
|
8111
|
-
|
|
8297
|
+
/* SADECE kullanıcının eklediği semboller görünür — artık varsayılan sembol YOK
|
|
8298
|
+
(eski davranış EURUSD/XAUUSD/GBPUSD/BTCUSD'yi geri geri getiriyordu) */
|
|
8299
|
+
if (!Array.isArray(f.symbols)) f.symbols = [];
|
|
8300
|
+
if (typeof f.consultChat !== 'boolean') f.consultChat = true; /* her tur öncesi chat ajanından plan */
|
|
8112
8301
|
if (!Number(f.intervalSec)) f.intervalSec = 120;
|
|
8113
8302
|
if (!Number(f.maxLot)) f.maxLot = 0.1;
|
|
8114
8303
|
if (f.maxPositions == null) f.maxPositions = 3;
|
|
@@ -8210,97 +8399,256 @@ mt5bridge.on('log', (m) => {
|
|
|
8210
8399
|
financeLog('[mt5] ' + line.slice(0, 300));
|
|
8211
8400
|
});
|
|
8212
8401
|
|
|
8213
|
-
/* ----------
|
|
8402
|
+
/* ---------- FINANCE AJANLARI (paralel ajan mimarisi) ----------
|
|
8403
|
+
Her finance ajanı = SÜREKLİ (continuous) paralel ajan: Paralel Ajanlar
|
|
8404
|
+
panelinde canlı görünür, hareketleri (tool-start/status) panelde akar,
|
|
8405
|
+
birden fazla ajan AYNI ANDA koşabilir (ana trader + sembol başına ayrı
|
|
8406
|
+
ajanlar). Döngüyü main yönetir; engine 'done'/'error'da sürekli işi
|
|
8407
|
+
KAPATMAZ (_bgFinish continuous dalı), yalnız kullanıcı iptali bitirir. */
|
|
8214
8408
|
|
|
8215
|
-
function
|
|
8216
|
-
|
|
8217
|
-
|
|
8218
|
-
|
|
8219
|
-
|
|
8220
|
-
|
|
8221
|
-
|
|
8222
|
-
|
|
8409
|
+
function finAgentSession(symbols, isMain) {
|
|
8410
|
+
if (isMain) {
|
|
8411
|
+
const sid = financeState.traderSid;
|
|
8412
|
+
if (sid) {
|
|
8413
|
+
try {
|
|
8414
|
+
const s = engine.cache.get(sid);
|
|
8415
|
+
if (s) return s;
|
|
8416
|
+
} catch {}
|
|
8417
|
+
financeState.traderSid = null;
|
|
8418
|
+
}
|
|
8223
8419
|
}
|
|
8224
8420
|
const s = engine._load(engine.createSession().id);
|
|
8225
8421
|
s.messages = s.messages || [];
|
|
8226
|
-
|
|
8422
|
+
const symLabel = symbols && symbols.length ? symbols.join(', ').slice(0, 40) : 'tüm liste';
|
|
8423
|
+
const title = isMain ? 'Beast Finance · Trader' : 'Finance · ' + symLabel;
|
|
8424
|
+
s.bgTitle = title; /* _view.isBg → sohbet geçmişinde gizli */
|
|
8227
8425
|
try {
|
|
8228
8426
|
fs.appendFileSync(
|
|
8229
8427
|
engine._file(s.id),
|
|
8230
|
-
JSON.stringify({ t: 'meta2', bgOf: '', title
|
|
8428
|
+
JSON.stringify({ t: 'meta2', bgOf: '', title, at: new Date().toISOString() }) + '\n'
|
|
8231
8429
|
);
|
|
8232
8430
|
} catch {}
|
|
8233
8431
|
engine.cache.set(s.id, s);
|
|
8234
|
-
engine.markFinance(s.id, true); /* kalıcı finance etiketi (
|
|
8235
|
-
s.workspace = financeDir(); /*
|
|
8236
|
-
financeState.traderSid = s.id;
|
|
8432
|
+
engine.markFinance(s.id, true); /* kalıcı finance etiketi (mt5_* araçları) */
|
|
8433
|
+
s.workspace = financeDir(); /* ajan kendi klasöründe çalışır */
|
|
8434
|
+
if (isMain) financeState.traderSid = s.id;
|
|
8237
8435
|
return s;
|
|
8238
8436
|
}
|
|
8239
8437
|
|
|
8240
|
-
|
|
8438
|
+
/* Ajanı Paralel Ajanlar paneline kaydet — sürekli (continuous) iş olarak.
|
|
8439
|
+
Panelde canlı hareket akışı + × ile durdurma buradan gelir. */
|
|
8440
|
+
function finAgentRegisterBg(s, symbols) {
|
|
8441
|
+
try {
|
|
8442
|
+
if (!engine || !engine._bgJobs) return;
|
|
8443
|
+
const f = finCfg();
|
|
8444
|
+
const symLabel = symbols && symbols.length ? symbols.join(', ').slice(0, 40) : 'tüm izleme listesi';
|
|
8445
|
+
engine._bgJobs.set(String(s.id), {
|
|
8446
|
+
id: s.id,
|
|
8447
|
+
code: s.code,
|
|
8448
|
+
title: s.bgTitle || 'Finance Ajanı',
|
|
8449
|
+
task: `Beast Finance ajanı (${symLabel}) — ${f.allowTrading ? 'otonom tur tur işlem yönetimi' : 'analiz modunda tur tur tarama'}`,
|
|
8450
|
+
agent: null,
|
|
8451
|
+
parentId: '',
|
|
8452
|
+
groupId: null,
|
|
8453
|
+
status: 'running',
|
|
8454
|
+
slot: false, /* eşzamanlılık slotu TÜKETMEZ — ana işleri bloklamaz */
|
|
8455
|
+
continuous: true, /* tur sonları işi KAPATMAZ (_bgFinish) */
|
|
8456
|
+
startedAt: new Date().toISOString(),
|
|
8457
|
+
lastActivityAt: new Date().toISOString(),
|
|
8458
|
+
lastNudgeAt: null,
|
|
8459
|
+
checks: 0,
|
|
8460
|
+
fixes: 0,
|
|
8461
|
+
endedAt: null,
|
|
8462
|
+
error: null,
|
|
8463
|
+
});
|
|
8464
|
+
engine._bgEmit();
|
|
8465
|
+
} catch {}
|
|
8466
|
+
}
|
|
8467
|
+
|
|
8468
|
+
function finApplyTraderFields(s, symbolsOverride) {
|
|
8241
8469
|
const f = finCfg();
|
|
8242
8470
|
s.finance = true;
|
|
8243
8471
|
s.financeTrader = true;
|
|
8244
8472
|
s.financeAuto = f.allowTrading === true;
|
|
8245
|
-
s.financeSymbols = f.symbols;
|
|
8473
|
+
s.financeSymbols = Array.isArray(symbolsOverride) && symbolsOverride.length ? symbolsOverride : f.symbols;
|
|
8246
8474
|
s.financeStrategy = String(f.strategy || '');
|
|
8247
8475
|
s.financeLimits = { maxLot: f.maxLot, maxPositions: f.maxPositions };
|
|
8248
8476
|
engine.cache.set(String(s.id), s);
|
|
8249
8477
|
}
|
|
8250
8478
|
|
|
8251
|
-
|
|
8479
|
+
/* Yeni finance ajanı aç (ana trader YA DA sembol işçisi) */
|
|
8480
|
+
function finAgentCreate(symbols, isMain) {
|
|
8252
8481
|
const f = finCfg();
|
|
8482
|
+
const s = finAgentSession(symbols, isMain);
|
|
8483
|
+
const record = {
|
|
8484
|
+
symbols: symbols && symbols.length ? symbols.slice(0, 10) : [...(f.symbols || [])],
|
|
8485
|
+
main: !!isMain,
|
|
8486
|
+
round: 0,
|
|
8487
|
+
timer: null,
|
|
8488
|
+
};
|
|
8489
|
+
financeState.agents.set(String(s.id), record);
|
|
8490
|
+
finApplyTraderFields(s, record.symbols);
|
|
8491
|
+
finAgentRegisterBg(s, record.symbols);
|
|
8492
|
+
return { s, agent: record };
|
|
8493
|
+
}
|
|
8494
|
+
|
|
8495
|
+
/* Ajanı durdur: timer kapat + oturumu kes + paralel ajan kaydını 'aborted' yaz */
|
|
8496
|
+
function finAgentStop(sid, reason) {
|
|
8497
|
+
sid = String(sid || '');
|
|
8498
|
+
const agent = financeState.agents.get(sid);
|
|
8499
|
+
if (!agent) return false;
|
|
8500
|
+
clearTimeout(agent.timer);
|
|
8501
|
+
financeState.agents.delete(sid);
|
|
8502
|
+
if (sid === String(financeState.traderSid || '')) financeState.traderSid = null;
|
|
8503
|
+
try {
|
|
8504
|
+
if (engine && engine.isBusy(sid)) {
|
|
8505
|
+
engine.interrupt(sid, reason || 'kullanıcı finance ajanını durdurdu');
|
|
8506
|
+
}
|
|
8507
|
+
} catch {}
|
|
8508
|
+
try {
|
|
8509
|
+
const j = engine && engine._bgJobs ? engine._bgJobs.get(sid) : null;
|
|
8510
|
+
if (j && j.status === 'running') {
|
|
8511
|
+
j.status = 'aborted';
|
|
8512
|
+
j.endedAt = new Date().toISOString();
|
|
8513
|
+
j.error = String(reason || 'kullanıcı durdurdu').slice(0, 200);
|
|
8514
|
+
engine._bgEmit();
|
|
8515
|
+
}
|
|
8516
|
+
} catch {}
|
|
8517
|
+
finPush('trader', { state: 'idle', rounds: financeState.traderRounds });
|
|
8518
|
+
return true;
|
|
8519
|
+
}
|
|
8520
|
+
|
|
8521
|
+
function finTraderBrief(agent) {
|
|
8522
|
+
const f = finCfg();
|
|
8523
|
+
const syms = agent && agent.symbols && agent.symbols.length ? agent.symbols.join(', ') : (f.symbols || []).join(', ');
|
|
8524
|
+
const who = agent && agent.main ? 'TRADER' : 'FINANCE AJANI';
|
|
8253
8525
|
return [
|
|
8254
|
-
|
|
8255
|
-
|
|
8526
|
+
`Beast Finance ${who} başlatıldı — ilk tur: strateji çerçeveni kur ve piyasa taramasını yap.`,
|
|
8527
|
+
`Odak semboller: ${syms || '(boş — mt5_status ile terminale bak, mantıklı semboller seç)'}`,
|
|
8256
8528
|
`Tur aralığı: ${f.intervalSec} sn · Max lot: ${f.maxLot} · Max eşzamanlı pozisyon: ${f.maxPositions}`,
|
|
8257
8529
|
f.allowTrading
|
|
8258
8530
|
? 'Otomatik işlem AÇIK: mt5_trade/mt5_close/mt5_modify/mt5_pending kullanabilirsin (limitler sistemce zorlanır).'
|
|
8259
8531
|
: 'Otomatik işlem KAPALI: SADECE analiz + net işlem önerileri yaz (sembol, yön, giriş, SL, TP, sebep); işlem açma.',
|
|
8260
8532
|
f.strategy ? `Sahibinin strateji notu: ${f.strategy}` : 'Strateji notu yok: trend + destek/direnç + momentum ile temel okuma yap.',
|
|
8261
8533
|
'Bu turda: mt5_status → hesap/pozisyon/fiyat verisi → değerlendirme → kararlar (veya BEKLE: sebep) → kısa rapor.',
|
|
8534
|
+
'Diğer finance/paralel ajanlarla koordinasyon için agent_dm aracı var (to: ajan başlığı anahtar kelimesi).',
|
|
8262
8535
|
].join('\n');
|
|
8263
8536
|
}
|
|
8264
8537
|
|
|
8265
|
-
|
|
8266
|
-
|
|
8267
|
-
|
|
8268
|
-
|
|
8269
|
-
|
|
8270
|
-
|
|
8538
|
+
/* Her tur öncesi CHAT AJANINDAN plan iste: aynı motorun odaklı alt-ajanı
|
|
8539
|
+
(_subagent) hesap/pozisyon/strateji bağlamıyla kısa işlem planı üretir.
|
|
8540
|
+
Plan, tur mesajına [ANA AJAN PLANI] bloğu olarak eklenir. */
|
|
8541
|
+
async function finConsultPlan(f, agent, sid) {
|
|
8542
|
+
if (!engine) return '';
|
|
8543
|
+
let account = null;
|
|
8544
|
+
let positions = [];
|
|
8545
|
+
try {
|
|
8546
|
+
if (mt5bridge.running) {
|
|
8547
|
+
const [ac, ps] = await Promise.all([
|
|
8548
|
+
mt5bridge.call('account', {}, 8000).catch(() => null),
|
|
8549
|
+
mt5bridge.call('positions', {}, 8000).catch(() => null),
|
|
8550
|
+
]);
|
|
8551
|
+
if (ac && ac.ok) account = ac.data && ac.data.account;
|
|
8552
|
+
if (ps && ps.ok) positions = (ps.data && ps.data.positions) || [];
|
|
8553
|
+
}
|
|
8554
|
+
} catch {}
|
|
8555
|
+
const auto = f.allowTrading ? 'AÇIK (işlem açabilir)' : 'KAPALI (yalnız öneri)';
|
|
8556
|
+
const task =
|
|
8557
|
+
'Beast Finance ajanı yeni tura giriyor. SEN mt5_* araçlarını KULLANMA, işlem AÇMA — ' +
|
|
8558
|
+
'sana düşen iş: semboller için bu turun kısa işlem PLANINI üretmek ' +
|
|
8559
|
+
'(sembol başına tek satır: AL/SAT/BEKLE + sebep + giriş/SL/TP fikri). ' +
|
|
8560
|
+
'Güncel piyasa bağlamı gerekiyorsa web_search kullan; kurulu bir skill konuyla ilgiliyse skill aracıyla oku.';
|
|
8561
|
+
const ctx = [
|
|
8562
|
+
`Odak semboller: ${agent && agent.symbols && agent.symbols.length ? agent.symbols.join(', ') : (f.symbols || []).join(', ') || '(boş — ajan kendi bulabilir)'}`,
|
|
8563
|
+
`Otomatik işlem: ${auto} · max lot ${f.maxLot} · max eşzamanlı pozisyon ${f.maxPositions}`,
|
|
8564
|
+
account
|
|
8565
|
+
? `Hesap: bakiye ${account.balance} ${account.currency} · özkaynak ${account.equity ?? '?'} · serbest marj ${account.margin_free ?? '?'} · kaldıraç 1:${account.leverage ?? '?'}`
|
|
8566
|
+
: 'Hesap verisi alınamadı.',
|
|
8567
|
+
positions.length
|
|
8568
|
+
? 'Açık pozisyonlar:\n' +
|
|
8569
|
+
positions
|
|
8570
|
+
.slice(0, 10)
|
|
8571
|
+
.map((p) => {
|
|
8572
|
+
const t = String(p.type ?? '');
|
|
8573
|
+
const side = /sell|1/.test(t) ? 'SATIŞ' : 'ALIŞ';
|
|
8574
|
+
return `- ${p.symbol || '?'} ${side} ${p.volume || '?'} lot @ ${p.price_open || '?'} · kâr/zarar ${p.profit ?? '?'}`;
|
|
8575
|
+
})
|
|
8576
|
+
.join('\n')
|
|
8577
|
+
: 'Açık pozisyon yok.',
|
|
8578
|
+
f.strategy ? `Sahibinin strateji notu: ${f.strategy}` : 'Strateji notu yok — temel teknik okuma (trend + destek/direnç + momentum).',
|
|
8579
|
+
'Plan EN FAZLA ~15 satır; planı işlem yapmadan sadece METİN olarak döndür.',
|
|
8580
|
+
].join('\n');
|
|
8581
|
+
try {
|
|
8582
|
+
const signal = typeof AbortSignal !== 'undefined' && AbortSignal.timeout ? AbortSignal.timeout(90000) : undefined;
|
|
8583
|
+
const res = await engine._subagent(task, ctx, signal, sid || null, '');
|
|
8584
|
+
return String(res || '').trim().slice(0, 4000);
|
|
8585
|
+
} catch {
|
|
8586
|
+
return '';
|
|
8587
|
+
}
|
|
8588
|
+
}
|
|
8589
|
+
|
|
8590
|
+
/* Bir ajanın TEK turu: (opsiyonel) ana ajan planı + tur emri */
|
|
8591
|
+
function finAgentRound(sid) {
|
|
8592
|
+
const agent = financeState.agents.get(String(sid));
|
|
8593
|
+
if (!agent || !engine) return;
|
|
8594
|
+
if (engine.isBusy(sid)) {
|
|
8271
8595
|
/* hâlâ çalışıyor — done eventinde tekrar planlanır */
|
|
8272
8596
|
return;
|
|
8273
8597
|
}
|
|
8274
|
-
|
|
8598
|
+
agent.round += 1;
|
|
8599
|
+
if (agent.main) financeState.traderRounds = agent.round;
|
|
8275
8600
|
const f = finCfg();
|
|
8601
|
+
try {
|
|
8602
|
+
const s = engine.cache.get(String(sid));
|
|
8603
|
+
if (s) finApplyTraderFields(s, agent.symbols);
|
|
8604
|
+
} catch {}
|
|
8276
8605
|
const auto = f.allowTrading
|
|
8277
8606
|
? 'İşlem açabilirsin — limitlere uy, SL\u2019siz pozisyon bırakma.'
|
|
8278
8607
|
: 'Otomatik işlem KAPALI — sadece analiz + öneri.';
|
|
8279
|
-
const
|
|
8280
|
-
|
|
8281
|
-
|
|
8282
|
-
|
|
8283
|
-
|
|
8284
|
-
|
|
8285
|
-
|
|
8286
|
-
|
|
8287
|
-
|
|
8288
|
-
|
|
8289
|
-
|
|
8290
|
-
|
|
8608
|
+
const focus = agent.symbols.length ? `Odak: ${agent.symbols.join(', ')}. ` : '';
|
|
8609
|
+
const round = `FINANCE TUR #${agent.round}: ${focus}hesap + pozisyonlar + fiyatları çek; açık pozisyonları yönet (SL/TP güncelle, hedefe ulaşanı kapat); stratejine göre yeni fırsatları değerlendir. ${auto} Kısa rapor ver.`;
|
|
8610
|
+
const launch = (planBlock) => {
|
|
8611
|
+
if (!financeState.agents.has(String(sid))) return;
|
|
8612
|
+
const ok = engine.send(sid, planBlock + round, { userAction: false });
|
|
8613
|
+
if (ok) {
|
|
8614
|
+
financeState.lastRoundAt = Date.now();
|
|
8615
|
+
if (agent.main) finPush('trader', { state: 'running', round: agent.round });
|
|
8616
|
+
} else {
|
|
8617
|
+
/* stop kapısı/yoğunluk — yarım dakika sonra sessizce tekrar dene */
|
|
8618
|
+
clearTimeout(agent.timer);
|
|
8619
|
+
agent.timer = setTimeout(() => { try { finAgentRound(sid); } catch {} }, 30000);
|
|
8620
|
+
}
|
|
8621
|
+
};
|
|
8622
|
+
if (f.consultChat === false) {
|
|
8623
|
+
/* danışma kapalı: tur doğrudan başlar */
|
|
8624
|
+
launch('');
|
|
8625
|
+
return;
|
|
8291
8626
|
}
|
|
8627
|
+
if (agent.main) finPush('trader', { state: 'consult', round: agent.round });
|
|
8628
|
+
finConsultPlan(f, agent, String(sid))
|
|
8629
|
+
.then((plan) => launch(plan ? `[ANA AJAN PLANI — bu turun varsayılan stratejisi; strateji notuyla çelişirse not önceliklidir]\n${plan}\n\n` : ''))
|
|
8630
|
+
.catch(() => launch(''));
|
|
8292
8631
|
}
|
|
8293
8632
|
|
|
8633
|
+
/* Tur/durum sonları: sürekli ajan döngüsünü besle; kullanıcı iptalinde kapat */
|
|
8294
8634
|
function finFlushOnDone(ev) {
|
|
8295
8635
|
if (!ev || (ev.type !== 'done' && ev.type !== 'error')) return;
|
|
8296
|
-
|
|
8297
|
-
|
|
8298
|
-
|
|
8636
|
+
const sid = String(ev.sessionId || '');
|
|
8637
|
+
const agent = financeState.agents.get(sid);
|
|
8638
|
+
if (!agent) return;
|
|
8639
|
+
/* Paralel Ajanlar panelinden × (interrupt abort'u) → ajanı gerçekten kapat */
|
|
8640
|
+
if (ev.type === 'done' && ev.aborted) {
|
|
8641
|
+
finAgentStop(sid, String(ev.reason || 'kullanıcı durdurdu'));
|
|
8642
|
+
return;
|
|
8643
|
+
}
|
|
8644
|
+
clearTimeout(agent.timer);
|
|
8299
8645
|
const f = finCfg();
|
|
8300
8646
|
const iv = Math.max(30, Number(f.intervalSec) || 120) * 1000;
|
|
8301
8647
|
financeState.lastRoundAt = Date.now();
|
|
8302
|
-
|
|
8303
|
-
|
|
8648
|
+
if (agent.main) {
|
|
8649
|
+
finPush('trader', { state: 'idle', round: agent.round, nextInSec: iv / 1000 });
|
|
8650
|
+
}
|
|
8651
|
+
agent.timer = setTimeout(() => { try { finAgentRound(sid); } catch {} }, iv);
|
|
8304
8652
|
}
|
|
8305
8653
|
|
|
8306
8654
|
ipcMain.handle('finance:state', async () => {
|
|
@@ -8407,36 +8755,76 @@ ipcMain.handle('finance:trader:start', async () => {
|
|
|
8407
8755
|
if (!engine) return { ok: false, error: 'ajan hazır değil' };
|
|
8408
8756
|
const f = finCfg();
|
|
8409
8757
|
if (!engine.publicState().hasModel && !f.traderSel) return { ok: false, error: 'model yok — Ayarlar → Provider' };
|
|
8410
|
-
|
|
8411
|
-
|
|
8412
|
-
|
|
8758
|
+
/* ANA trader: varsa aynen sürdür, yoksa yeni sürekli ajan aç */
|
|
8759
|
+
let mainSid = '';
|
|
8760
|
+
let mainAgent = null;
|
|
8761
|
+
for (const [sid, a] of financeState.agents) {
|
|
8762
|
+
if (a.main) {
|
|
8763
|
+
mainSid = sid;
|
|
8764
|
+
mainAgent = a;
|
|
8765
|
+
break;
|
|
8766
|
+
}
|
|
8767
|
+
}
|
|
8768
|
+
if (!mainSid) {
|
|
8769
|
+
const created = finAgentCreate([], true);
|
|
8770
|
+
mainSid = String(created.s.id);
|
|
8771
|
+
mainAgent = created.agent;
|
|
8772
|
+
}
|
|
8773
|
+
try { engine.setSessionModel(mainSid, f.traderSel || null); } catch {}
|
|
8413
8774
|
financeState.traderOn = true;
|
|
8414
|
-
financeState.traderRounds = 0;
|
|
8415
8775
|
try { engine.clearStop(); } catch {}
|
|
8416
|
-
const ok = engine.send(
|
|
8776
|
+
const ok = engine.send(mainSid, finTraderBrief(mainAgent), { userAction: true });
|
|
8417
8777
|
if (!ok) {
|
|
8418
8778
|
financeState.traderOn = false;
|
|
8419
8779
|
return { ok: false, error: 'trader oturumu meşgul — birkaç saniye sonra tekrar dene' };
|
|
8420
8780
|
}
|
|
8421
8781
|
financeState.lastRoundAt = Date.now();
|
|
8422
8782
|
financeLog('[trader] başlatıldı (model: ' + (f.traderSel || 'genel aktif model') + ')');
|
|
8423
|
-
finPush('trader', { state: 'running', round:
|
|
8424
|
-
return { ok: true, sid:
|
|
8783
|
+
finPush('trader', { state: 'running', round: mainAgent.round });
|
|
8784
|
+
return { ok: true, sid: mainSid };
|
|
8425
8785
|
});
|
|
8426
8786
|
|
|
8427
8787
|
ipcMain.handle('finance:trader:stop', async () => {
|
|
8428
8788
|
financeState.traderOn = false;
|
|
8429
|
-
|
|
8430
|
-
|
|
8431
|
-
|
|
8432
|
-
|
|
8433
|
-
|
|
8789
|
+
/* ANA trader'ı durdur (sembol işçileri rail × ile ayrı durdurulur) */
|
|
8790
|
+
let mainSid = '';
|
|
8791
|
+
for (const [sid, a] of financeState.agents) {
|
|
8792
|
+
if (a.main) {
|
|
8793
|
+
mainSid = sid;
|
|
8794
|
+
break;
|
|
8795
|
+
}
|
|
8434
8796
|
}
|
|
8797
|
+
const stopped = mainSid ? finAgentStop(mainSid, 'kullanıcı Beast Finance trader ajanını durdurdu') : false;
|
|
8798
|
+
const interrupted = !!stopped;
|
|
8435
8799
|
financeLog('[trader] durduruldu');
|
|
8436
8800
|
finPush('trader', { state: 'stopped' });
|
|
8437
8801
|
return { ok: true, interrupted };
|
|
8438
8802
|
});
|
|
8439
8803
|
|
|
8804
|
+
/* SEMBOL İŞÇİSİ: PİYASA kartındaki ▶ ile sembol başına AYRI finance ajanı —
|
|
8805
|
+
hepsi AYNI ANDA koşar, Paralel Ajanlar panelinde canlı görünür */
|
|
8806
|
+
ipcMain.handle('finance:agent:spawn', async (_e, payload) => {
|
|
8807
|
+
if (!engine) return { ok: false, error: 'ajan hazır değil' };
|
|
8808
|
+
const f = finCfg();
|
|
8809
|
+
if (!engine.publicState().hasModel && !f.traderSel) return { ok: false, error: 'model yok — Ayarlar → Provider' };
|
|
8810
|
+
const symbol = String((payload && payload.symbol) || '').trim().toUpperCase();
|
|
8811
|
+
if (!symbol) return { ok: false, error: 'sembol gerekli' };
|
|
8812
|
+
for (const [, a] of financeState.agents) {
|
|
8813
|
+
if (!a.main && a.symbols.length === 1 && a.symbols[0] === symbol) {
|
|
8814
|
+
return { ok: false, error: symbol + ' için ajan zaten koşuyor (Paralel Ajanlar panelinden durdurabilirsin)' };
|
|
8815
|
+
}
|
|
8816
|
+
}
|
|
8817
|
+
const created = finAgentCreate([symbol], false);
|
|
8818
|
+
try { engine.setSessionModel(created.s.id, f.traderSel || null); } catch {}
|
|
8819
|
+
const ok = engine.send(created.s.id, finTraderBrief(created.agent), { userAction: true });
|
|
8820
|
+
if (!ok) {
|
|
8821
|
+
finAgentStop(String(created.s.id), 'oturum meşgul — ajan başlatılamadı');
|
|
8822
|
+
return { ok: false, error: 'ajan oturumu meşgul — birkaç saniye sonra tekrar dene' };
|
|
8823
|
+
}
|
|
8824
|
+
financeLog('[ajan] ' + symbol + ' işçisi başlatıldı');
|
|
8825
|
+
return { ok: true, sid: created.s.id, title: created.s.bgTitle };
|
|
8826
|
+
});
|
|
8827
|
+
|
|
8440
8828
|
ipcMain.handle('finance:connect', async () => {
|
|
8441
8829
|
mt5bridge.stop();
|
|
8442
8830
|
const st = financeEnsureBridge();
|