beast-agent 2.22.2 → 2.23.1
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 +225 -85
- package/src/main.js +20 -2
- package/src/renderer/renderer.js +79 -35
- package/src/renderer/style.css +12 -0
package/package.json
CHANGED
package/src/agent/engine.js
CHANGED
|
@@ -364,9 +364,17 @@ class Engine {
|
|
|
364
364
|
(AJAN DM paneli buradan beslenir; restart sonrası da durur) */
|
|
365
365
|
this._agentDms = [];
|
|
366
366
|
this._agentDmsFile = path.join(this.sessionsDir, 'agent-dms.json');
|
|
367
|
+
/* AJAN DM GRUPLARI: ajanların agent_dm ile kurduğu grup sohbetleri
|
|
368
|
+
(id → {id,title,members,titles,createdAt,closed,closedAt}) */
|
|
369
|
+
this._agentGroups = new Map();
|
|
367
370
|
try {
|
|
368
371
|
const raw = JSON.parse(fs.readFileSync(this._agentDmsFile, 'utf8'));
|
|
369
372
|
this._agentDms = Array.isArray(raw.dms) ? raw.dms : [];
|
|
373
|
+
if (raw.groups && typeof raw.groups === 'object') {
|
|
374
|
+
for (const g of Object.values(raw.groups)) {
|
|
375
|
+
if (g && g.id && g.title) this._agentGroups.set(String(g.id), g);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
370
378
|
} catch {
|
|
371
379
|
this._agentDms = [];
|
|
372
380
|
}
|
|
@@ -1774,7 +1782,7 @@ class Engine {
|
|
|
1774
1782
|
} catch {
|
|
1775
1783
|
return null;
|
|
1776
1784
|
}
|
|
1777
|
-
}
|
|
1785
|
+
}
|
|
1778
1786
|
|
|
1779
1787
|
/* Beast Studio (video paneli): video YAPMA ve DÜZENLEME ajanı. Workspace'teki
|
|
1780
1788
|
her şey video proje malzemesi olarak görülür (ham görüntü, ses, görsel,
|
|
@@ -1874,7 +1882,7 @@ class Engine {
|
|
|
1874
1882
|
modeBlock2 + '\n' +
|
|
1875
1883
|
'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' +
|
|
1876
1884
|
'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' +
|
|
1877
|
-
'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' +
|
|
1885
|
+
'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"; ortak karar için group: "İSİM" ile grup sohbeti kur — mesaj tüm üyelere düşer). Görevin bitince DM/grup sohbetleri otomatik KAPANIR (geçmiş panelde kalır).\n' +
|
|
1878
1886
|
(symbols ? `İZLEME LİSTESİ: ${symbols}\n` : '') +
|
|
1879
1887
|
(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' : '') +
|
|
1880
1888
|
'RİSK DİSİPLİNİ:\n' +
|
|
@@ -2710,7 +2718,13 @@ class Engine {
|
|
|
2710
2718
|
}
|
|
2711
2719
|
this.emit({ type: 'message', sessionId: s.id, message: msg });
|
|
2712
2720
|
this.emit({ type: 'sessions' });
|
|
2713
|
-
|
|
2721
|
+
/* sessiz catch YOK: tur kurulumu patlarsa hata UI'ya düşsün —
|
|
2722
|
+
"ajan cvp vermiyor, hata görünmüyor" bu yüzdendi */
|
|
2723
|
+
this._run(s).catch((e) => {
|
|
2724
|
+
try {
|
|
2725
|
+
this.emit({ type: 'error', sessionId: s.id, error: String((e && e.message) || e) });
|
|
2726
|
+
} catch {}
|
|
2727
|
+
});
|
|
2714
2728
|
return true;
|
|
2715
2729
|
}
|
|
2716
2730
|
|
|
@@ -3066,6 +3080,19 @@ class Engine {
|
|
|
3066
3080
|
|
|
3067
3081
|
/* #17 iş kayıtlarını diske yaz — uygulama kapansa da geçmiş dursun.
|
|
3068
3082
|
Oturum dosyası silinmiş işler (elle silinmiş) geri yazılmaz. */
|
|
3083
|
+
/* ATOMİK YAZIM: önce .tmp sonra rename — iki instance çakışsın ya da
|
|
3084
|
+
proses yazım ortasında ölse bile dosya ASLA 0 bayt bozulmaz
|
|
3085
|
+
(bg-jobs.json 0 bayt olunca tüm ajanlar kayboluyordu) */
|
|
3086
|
+
_atomicWrite(file, data) {
|
|
3087
|
+
try {
|
|
3088
|
+
const tmp = file + '.tmp';
|
|
3089
|
+
fs.writeFileSync(tmp, data);
|
|
3090
|
+
fs.renameSync(tmp, file);
|
|
3091
|
+
} catch {
|
|
3092
|
+
try { fs.writeFileSync(file, data); } catch {}
|
|
3093
|
+
}
|
|
3094
|
+
}
|
|
3095
|
+
|
|
3069
3096
|
_saveBgJobs() {
|
|
3070
3097
|
try {
|
|
3071
3098
|
const jobs = [];
|
|
@@ -3075,7 +3102,7 @@ class Engine {
|
|
|
3075
3102
|
delete c._persisted;
|
|
3076
3103
|
jobs.push(c);
|
|
3077
3104
|
}
|
|
3078
|
-
|
|
3105
|
+
this._atomicWrite(this._bgJobsFile, JSON.stringify({ v: 1, jobs }) + '\n');
|
|
3079
3106
|
} catch {}
|
|
3080
3107
|
}
|
|
3081
3108
|
|
|
@@ -3181,6 +3208,8 @@ class Engine {
|
|
|
3181
3208
|
}
|
|
3182
3209
|
/* #17 öz-kurtarma hakları tükendi → owner'a anlık altyapı uyarısı */
|
|
3183
3210
|
this._notifyOwnerTaskFailed(job, msg);
|
|
3211
|
+
/* AJAN DM: işi kesin bitti — ajanın DM/grup sohbetleri kapanır */
|
|
3212
|
+
try { this._agentDmClose(sid); } catch {}
|
|
3184
3213
|
if (grouped) {
|
|
3185
3214
|
/* #18 hatalı üye de gruba yazılır — birleşik raporda görünecek */
|
|
3186
3215
|
this._bgGroupRecord(job.title, 'error', 'HATA: ' + msg.slice(0, 300));
|
|
@@ -3204,6 +3233,11 @@ class Engine {
|
|
|
3204
3233
|
this._pendingReports.push({ parentId: job.parentId, text });
|
|
3205
3234
|
this.flushPendingReports(job.parentId);
|
|
3206
3235
|
}
|
|
3236
|
+
/* AJAN DM: işi bitti (kullanıcı iptali dahil) — sohbetler kapanır */
|
|
3237
|
+
try { this._agentDmClose(sid); } catch {}
|
|
3238
|
+
} else if (status === 'done') {
|
|
3239
|
+
/* AJAN DM: görev tamamlandı — ajanın DM/grup sohbetleri kapanır */
|
|
3240
|
+
try { this._agentDmClose(sid); } catch {}
|
|
3207
3241
|
}
|
|
3208
3242
|
}
|
|
3209
3243
|
|
|
@@ -3464,7 +3498,7 @@ class Engine {
|
|
|
3464
3498
|
`- Bağımsız araç çağrılarını aynı turda PARALEL ver.\n` +
|
|
3465
3499
|
`- 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` +
|
|
3466
3500
|
`- 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` +
|
|
3467
|
-
`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` +
|
|
3501
|
+
`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. Birden fazla ajanla ORTAK KARAR almanız gerekiyorsa group: "İSİM" vererek grup sohbeti kur — mesajın tüm üyelere düşer, cevaplar aynı gruba gelir. Görevin bitince DM/grup sohbetleri otomatik KAPANIR (geçmiş panelde kalır).\n` +
|
|
3468
3502
|
FORMAT_RULES + '\n' +
|
|
3469
3503
|
`SON ÇIKTI: 3-5 satırlık net sonuç raporu, madde madde. Soru sorma, sohbet etme.`
|
|
3470
3504
|
);
|
|
@@ -3551,7 +3585,7 @@ class Engine {
|
|
|
3551
3585
|
? this.buildStudioSystem(session)
|
|
3552
3586
|
: bcAgent
|
|
3553
3587
|
? this.buildOcSystem(session, bcAgent)
|
|
3554
|
-
: await this.buildSystem(promptText, session);
|
|
3588
|
+
: await this.buildSystem(promptText, session);
|
|
3555
3589
|
// Granül izin: oturumun yetki seviyesine göre araç seti daraltılır.
|
|
3556
3590
|
// Çoklu izin (ör. web+read) seçiliyse kümeler BİRLEŞİR — hepsinin araçları açık olur.
|
|
3557
3591
|
// BC'de opencode adları Beast adına çevrilerek denetlenir (read → read_file).
|
|
@@ -4061,7 +4095,7 @@ class Engine {
|
|
|
4061
4095
|
}
|
|
4062
4096
|
}
|
|
4063
4097
|
} catch {}
|
|
4064
|
-
}
|
|
4098
|
+
}
|
|
4065
4099
|
// Ekran görüntüsü gibi araçlar görseli sonraki tura enjekte eder
|
|
4066
4100
|
let injectedImage = null;
|
|
4067
4101
|
/* opencode ctx.metadata portu: edit/write sonucundaki diffView, LLM
|
|
@@ -5377,18 +5411,19 @@ const AGENT_DM_DEF = {
|
|
|
5377
5411
|
function: {
|
|
5378
5412
|
name: 'agent_dm',
|
|
5379
5413
|
description:
|
|
5380
|
-
'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"). `topic` = short subject label — replies to the same topic stay in the SAME conversation thread, so ALWAYS reuse the topic you were DMed with when replying.',
|
|
5414
|
+
'Send a short DM to another running agent (parallel agents / finance agents) to coordinate: share findings, ask status, warn about risk, hand off work, and MAKE JOINT DECISIONS. `to` = target session id OR a keyword from the agent title (e.g. "GOLD", "Trader"). `topic` = short subject label — replies to the same topic stay in the SAME conversation thread, so ALWAYS reuse the topic you were DMed with when replying. `group` = optional group-chat name (e.g. "GOLD EKIP"): creates or reuses a group conversation, adds the target agent as a member, and your message is delivered to EVERY member — use groups when a decision needs multiple agents.',
|
|
5381
5415
|
parameters: {
|
|
5382
5416
|
type: 'object',
|
|
5383
5417
|
properties: {
|
|
5384
5418
|
to: { type: 'string', description: 'Target agent session id or a title keyword' },
|
|
5385
5419
|
topic: { type: 'string', description: 'Short subject label for the conversation thread, e.g. "GOLD pozisyon riski"' },
|
|
5386
5420
|
message: { type: 'string', description: 'Short message (1-3 sentences)' },
|
|
5421
|
+
group: { type: 'string', description: 'Optional group-chat name — send to ALL members of that group (creates it on first use, adds the target agent)' },
|
|
5387
5422
|
},
|
|
5388
|
-
required: ['
|
|
5423
|
+
required: ['message'],
|
|
5389
5424
|
},
|
|
5390
5425
|
},
|
|
5391
|
-
};
|
|
5426
|
+
};
|
|
5392
5427
|
|
|
5393
5428
|
const TOOLS = [
|
|
5394
5429
|
...tools.definitions,
|
|
@@ -6180,89 +6215,146 @@ Engine.prototype._agentDmSend = function (fromSid, args) {
|
|
|
6180
6215
|
const to = String((args && args.to) || '').trim();
|
|
6181
6216
|
const text = String((args && args.message) || '').trim().slice(0, 1200);
|
|
6182
6217
|
if (!text) return { ok: false, error: 'mesaj boş' };
|
|
6218
|
+
const groupName = String((args && args.group) || '').replace(/\s+/g, ' ').trim().slice(0, 40);
|
|
6183
6219
|
const jobs = this._bgJobs || new Map();
|
|
6184
|
-
|
|
6185
|
-
|
|
6186
|
-
|
|
6187
|
-
|
|
6188
|
-
|
|
6189
|
-
|
|
6190
|
-
|
|
6191
|
-
|
|
6220
|
+
const fromJob = jobs.get(String(fromSid));
|
|
6221
|
+
/* gönderici koşan ajan değilse (düz chat oturumu) cache'ten oturum bilgisi */
|
|
6222
|
+
const fromSess = fromJob ? null : this.cache.get(String(fromSid)) || null;
|
|
6223
|
+
const fromTitle =
|
|
6224
|
+
fromJob && fromJob.title
|
|
6225
|
+
? String(fromJob.title)
|
|
6226
|
+
: fromSess
|
|
6227
|
+
? 'Beast · ' + String(fromSess.bgTitle || fromSess.title || fromSess.code || '')
|
|
6228
|
+
: 'Ajan';
|
|
6229
|
+
/* hedef çöz: oturum id YA DA başlık anahtar kelimesi (koşan ajanlar +
|
|
6230
|
+
ANA OTURUM FALLBACK: chat oturumları this.cache'te yaşar — finance
|
|
6231
|
+
trader → ana Beast DM zinciri böyle kapanır). */
|
|
6232
|
+
let target = '';
|
|
6233
|
+
if (to) {
|
|
6234
|
+
target = jobs.has(to) ? to : '';
|
|
6235
|
+
if (!target) {
|
|
6236
|
+
const q = to.toLowerCase();
|
|
6237
|
+
for (const [sid, j] of jobs) {
|
|
6238
|
+
if (sid === String(fromSid)) continue;
|
|
6239
|
+
if (j && j.title && String(j.title).toLowerCase().includes(q)) {
|
|
6240
|
+
target = sid;
|
|
6241
|
+
break;
|
|
6242
|
+
}
|
|
6192
6243
|
}
|
|
6193
6244
|
}
|
|
6194
|
-
|
|
6195
|
-
|
|
6196
|
-
|
|
6197
|
-
|
|
6198
|
-
|
|
6199
|
-
|
|
6200
|
-
|
|
6201
|
-
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
|
|
6245
|
+
if (!target) {
|
|
6246
|
+
const q = to.toLowerCase();
|
|
6247
|
+
for (const [sid, s] of this.cache) {
|
|
6248
|
+
if (sid === String(fromSid)) continue;
|
|
6249
|
+
if (!s || s.bgJob || s.isBotDm) continue;
|
|
6250
|
+
if (s.botId && s.botId !== 'beast') continue;
|
|
6251
|
+
/* tam başlık da eşleşir: ajan DM'den gelen "Beast · KOD" başlığıyla
|
|
6252
|
+
cevap verir — fromTitle formatı birebir tanınmalı */
|
|
6253
|
+
const hay = [s.bgTitle, s.title, s.code, s.code ? 'beast · ' + s.code : ''].map((x) =>
|
|
6254
|
+
String(x || '').toLowerCase()
|
|
6255
|
+
);
|
|
6256
|
+
if (hay.some((x) => x && x.includes(q))) {
|
|
6257
|
+
target = sid;
|
|
6258
|
+
break;
|
|
6259
|
+
}
|
|
6208
6260
|
}
|
|
6209
6261
|
}
|
|
6210
|
-
|
|
6211
|
-
|
|
6212
|
-
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
|
|
6217
|
-
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
|
|
6221
|
-
)
|
|
6222
|
-
|
|
6223
|
-
|
|
6224
|
-
|
|
6225
|
-
|
|
6226
|
-
|
|
6262
|
+
if (String(target) === String(fromSid)) return { ok: false, error: 'kendine DM atılamaz' };
|
|
6263
|
+
if (!target) {
|
|
6264
|
+
const titles = [...jobs.values()]
|
|
6265
|
+
.filter((j) => j && j.status === 'running' && j.title)
|
|
6266
|
+
.map((j) => j.title)
|
|
6267
|
+
.slice(0, 8);
|
|
6268
|
+
return {
|
|
6269
|
+
ok: false,
|
|
6270
|
+
error: 'hedef ajan bulunamadı: "' + to + '"' + (titles.length ? ' — koşan ajanlar: ' + titles.join(', ') : ''),
|
|
6271
|
+
};
|
|
6272
|
+
}
|
|
6273
|
+
} else if (!groupName) {
|
|
6274
|
+
return { ok: false, error: 'to (hedef ajan) ya da group (grup adı) gerekli' };
|
|
6275
|
+
}
|
|
6276
|
+
const toJob = target ? jobs.get(target) : null;
|
|
6277
|
+
const mainChatSess = target && !toJob ? this.cache.get(String(target)) || null : null;
|
|
6278
|
+
const toTitle = target
|
|
6279
|
+
? toJob
|
|
6280
|
+
? String((toJob && toJob.title) || 'Ajan')
|
|
6281
|
+
: String((mainChatSess && (mainChatSess.bgTitle || mainChatSess.title)) || 'Beast')
|
|
6282
|
+
: '';
|
|
6283
|
+
/* konu etiketi: aynı konudaki DM'ler panelde AYNI oturumda toplanır */
|
|
6284
|
+
const topic = String((args && args.topic) || '').replace(/\s+/g, ' ').trim().slice(0, 60) || '(genel)';
|
|
6285
|
+
const at = new Date().toISOString();
|
|
6286
|
+
|
|
6287
|
+
/* GRUP SOHBETİ: kur/yeniden kullan, hedefi üye yap, mesaj HER ÜYEYE düşer.
|
|
6288
|
+
Ajanlar ortak kararları burada tartışır; iş bitince sohbet kapanır. */
|
|
6289
|
+
if (groupName) {
|
|
6290
|
+
/* gid Türkçe-kararsız normalize: ALTIN/Altın/altın aynı grup */
|
|
6291
|
+
const gid =
|
|
6292
|
+
'grp:' +
|
|
6293
|
+
groupName
|
|
6294
|
+
.toLowerCase()
|
|
6295
|
+
.replace(/\u0307/g, '')
|
|
6296
|
+
.replace(/ı/g, 'i')
|
|
6297
|
+
.replace(/ğ/g, 'g')
|
|
6298
|
+
.replace(/ü/g, 'u')
|
|
6299
|
+
.replace(/ş/g, 's')
|
|
6300
|
+
.replace(/ö/g, 'o')
|
|
6301
|
+
.replace(/ç/g, 'c');
|
|
6302
|
+
let g = this._agentGroups.get(gid);
|
|
6303
|
+
if (!g) {
|
|
6304
|
+
if (!target) return { ok: false, error: 'yeni grup için to ile en az bir ajan belirt' };
|
|
6305
|
+
g = { id: gid, title: groupName, members: [], titles: {}, createdAt: at, closed: false };
|
|
6306
|
+
this._agentGroups.set(gid, g);
|
|
6307
|
+
}
|
|
6308
|
+
g.titles = g.titles || {};
|
|
6309
|
+
const addMember = (sid, title) => {
|
|
6310
|
+
sid = String(sid);
|
|
6311
|
+
if (!g.members.includes(sid)) g.members.push(sid);
|
|
6312
|
+
if (title) g.titles[sid] = title;
|
|
6313
|
+
};
|
|
6314
|
+
addMember(fromSid, fromTitle);
|
|
6315
|
+
if (target) addMember(target, toTitle);
|
|
6316
|
+
g.closed = false; /* yeni mesaj → sohbet yeniden aktif */
|
|
6317
|
+
delete g.closedAt;
|
|
6318
|
+
const dm = {
|
|
6319
|
+
at,
|
|
6320
|
+
from: String(fromSid),
|
|
6321
|
+
fromTitle,
|
|
6322
|
+
to: target || '',
|
|
6323
|
+
toTitle: target ? toTitle : 'grup',
|
|
6324
|
+
group: gid,
|
|
6325
|
+
groupTitle: g.title,
|
|
6326
|
+
topic,
|
|
6327
|
+
text,
|
|
6328
|
+
};
|
|
6329
|
+
this._pushAgentDm(dm);
|
|
6330
|
+
for (const m of g.members) {
|
|
6331
|
+
if (m === String(fromSid) || !jobs.has(m)) continue;
|
|
6332
|
+
emitSafe(this, m, { type: 'agent-dm', ...dm });
|
|
6333
|
+
(this._pendingReports = this._pendingReports || []).push({
|
|
6334
|
+
parentId: m,
|
|
6335
|
+
text:
|
|
6336
|
+
`[AJAN DM (grup: "${g.title}") — ${dm.fromTitle} · konu: "${topic}"]\n${text}\n` +
|
|
6337
|
+
`(Cevabını agent_dm aracıyla ver — group: "${g.title}", to: "${dm.fromTitle}", topic: "${topic}")`,
|
|
6338
|
+
});
|
|
6339
|
+
this.flushPendingReports(m);
|
|
6340
|
+
}
|
|
6341
|
+
this._persistAgentDms();
|
|
6227
6342
|
return {
|
|
6228
|
-
ok:
|
|
6229
|
-
|
|
6343
|
+
ok: true,
|
|
6344
|
+
group: g.title,
|
|
6345
|
+
members: g.members.map((m) => g.titles[m] || m),
|
|
6346
|
+
...(target ? { to: target, toTitle } : {}),
|
|
6347
|
+
topic,
|
|
6230
6348
|
};
|
|
6231
6349
|
}
|
|
6232
|
-
|
|
6233
|
-
|
|
6234
|
-
|
|
6235
|
-
const
|
|
6236
|
-
|
|
6237
|
-
|
|
6238
|
-
? String(fromJob.title)
|
|
6239
|
-
: fromSess
|
|
6240
|
-
? 'Beast · ' + String(fromSess.code || '')
|
|
6241
|
-
: 'Ajan';
|
|
6242
|
-
const toTitle = targetIsAgent
|
|
6243
|
-
? String((toJob && toJob.title) || 'Ajan')
|
|
6244
|
-
: String((mainChatSess && (mainChatSess.bgTitle || mainChatSess.title)) || 'Beast');
|
|
6245
|
-
const dm = {
|
|
6246
|
-
at: new Date().toISOString(),
|
|
6247
|
-
from: String(fromSid),
|
|
6248
|
-
fromTitle,
|
|
6249
|
-
to: String(target),
|
|
6250
|
-
toTitle,
|
|
6251
|
-
topic,
|
|
6252
|
-
text,
|
|
6253
|
-
};
|
|
6254
|
-
this._agentDms = this._agentDms || [];
|
|
6255
|
-
this._agentDms.push(dm);
|
|
6256
|
-
if (this._agentDms.length > 400) this._agentDms.splice(0, this._agentDms.length - 400);
|
|
6257
|
-
try {
|
|
6258
|
-
fs.writeFileSync(this._agentDmsFile, JSON.stringify({ dms: this._agentDms }, null, 2));
|
|
6259
|
-
} catch {}
|
|
6350
|
+
|
|
6351
|
+
/* BİREYSEL DM — DÜZ CHAT OTURUMUNA (sahibin sohbet geçmişi) ENJEKTE
|
|
6352
|
+
ETMEYİZ: DM trafik yalnız AJAN DM konsolunda görünür. */
|
|
6353
|
+
const dm = { at, from: String(fromSid), fromTitle, to: String(target), toTitle, topic, text };
|
|
6354
|
+
this._pushAgentDm(dm);
|
|
6355
|
+
this._persistAgentDms();
|
|
6260
6356
|
emitSafe(this, target, { type: 'agent-dm', ...dm });
|
|
6261
|
-
|
|
6262
|
-
meşgulse pending kuyruğunda bekler, done olunca görünür.
|
|
6263
|
-
DÜZ CHAT OTURUMUNA (sahibin sohbet geçmişi) ENJEKTE ETMEYİZ — DM trafik
|
|
6264
|
-
yalnız AJAN DM konsolunda görünür, senin sohbetlerin karışmaz. */
|
|
6265
|
-
if (targetIsAgent) {
|
|
6357
|
+
if (jobs.has(target)) {
|
|
6266
6358
|
(this._pendingReports = this._pendingReports || []).push({
|
|
6267
6359
|
parentId: target,
|
|
6268
6360
|
text:
|
|
@@ -6277,12 +6369,60 @@ Engine.prototype._agentDmSend = function (fromSid, args) {
|
|
|
6277
6369
|
}
|
|
6278
6370
|
};
|
|
6279
6371
|
|
|
6372
|
+
Engine.prototype._pushAgentDm = function (dm) {
|
|
6373
|
+
this._agentDms = this._agentDms || [];
|
|
6374
|
+
this._agentDms.push(dm);
|
|
6375
|
+
if (this._agentDms.length > 600) this._agentDms.splice(0, this._agentDms.length - 600);
|
|
6376
|
+
};
|
|
6377
|
+
|
|
6378
|
+
Engine.prototype._persistAgentDms = function () {
|
|
6379
|
+
try {
|
|
6380
|
+
const groups = {};
|
|
6381
|
+
for (const [, g] of this._agentGroups || new Map()) groups[g.id] = g;
|
|
6382
|
+
this._atomicWrite(this._agentDmsFile, JSON.stringify({ dms: this._agentDms, groups }, null, 2));
|
|
6383
|
+
} catch {}
|
|
6384
|
+
};
|
|
6385
|
+
|
|
6386
|
+
/* AJAN İŞİ BİTİNCE: o ajanın bireysel DM'leri kapanır; üyesi olduğu
|
|
6387
|
+
gruplarda DİĞER koşan ajan kalmadıysa grup sohbeti de kapanır.
|
|
6388
|
+
Geçmiş panelde GÖRÜNMEYE DEVAM EDER (silinmez, "Geçmiş" bölümüne düşer). */
|
|
6389
|
+
Engine.prototype._agentDmClose = function (sid) {
|
|
6390
|
+
sid = String(sid);
|
|
6391
|
+
const now = new Date().toISOString();
|
|
6392
|
+
let changed = false;
|
|
6393
|
+
for (const dm of this._agentDms || []) {
|
|
6394
|
+
if (dm.closed || dm.group) continue;
|
|
6395
|
+
if (String(dm.from) === sid || String(dm.to) === sid) {
|
|
6396
|
+
dm.closed = true;
|
|
6397
|
+
dm.closedAt = now;
|
|
6398
|
+
changed = true;
|
|
6399
|
+
}
|
|
6400
|
+
}
|
|
6401
|
+
const jobs = this._bgJobs || new Map();
|
|
6402
|
+
for (const [, g] of this._agentGroups || new Map()) {
|
|
6403
|
+
if (g.closed || !(g.members || []).includes(sid)) continue;
|
|
6404
|
+
const anyRunning = g.members.some((m) => {
|
|
6405
|
+
const j = jobs.get(m);
|
|
6406
|
+
return j && j.status === 'running';
|
|
6407
|
+
});
|
|
6408
|
+
if (!anyRunning) {
|
|
6409
|
+
g.closed = true;
|
|
6410
|
+
g.closedAt = now;
|
|
6411
|
+
changed = true;
|
|
6412
|
+
}
|
|
6413
|
+
}
|
|
6414
|
+
if (changed) this._persistAgentDms();
|
|
6415
|
+
};
|
|
6416
|
+
|
|
6280
6417
|
Engine.prototype.agentDmsList = function () {
|
|
6281
|
-
|
|
6418
|
+
const groups = [];
|
|
6419
|
+
for (const [, g] of this._agentGroups || new Map()) groups.push(g);
|
|
6420
|
+
return { dms: [...(this._agentDms || [])].slice(-600), groups };
|
|
6282
6421
|
};
|
|
6283
6422
|
|
|
6284
6423
|
Engine.prototype.agentDmsClear = function () {
|
|
6285
6424
|
this._agentDms = [];
|
|
6425
|
+
this._agentGroups = new Map();
|
|
6286
6426
|
try { fs.rmSync(this._agentDmsFile, { force: true }); } catch {}
|
|
6287
6427
|
return { ok: true };
|
|
6288
6428
|
};
|
package/src/main.js
CHANGED
|
@@ -8764,6 +8764,15 @@ ipcMain.handle('finance:mode', async (_e, payload) => {
|
|
|
8764
8764
|
}
|
|
8765
8765
|
} catch {}
|
|
8766
8766
|
}
|
|
8767
|
+
if (financeState.mode) {
|
|
8768
|
+
/* OTOMATİK TRADER: finance modu açılır açılmaz trader botu devreye girer
|
|
8769
|
+
(best-effort — model/köprü yoksa sessizce atlanır, log'a düşer) */
|
|
8770
|
+
financeTraderStart()
|
|
8771
|
+
.then((r) => {
|
|
8772
|
+
if (!r || !r.ok) financeLog('[trader] otomatik başlatılamadı: ' + ((r && r.error) || 'bilinmeyen'));
|
|
8773
|
+
})
|
|
8774
|
+
.catch(() => {});
|
|
8775
|
+
}
|
|
8767
8776
|
return { ok: true, mode: financeState.mode, needNew };
|
|
8768
8777
|
});
|
|
8769
8778
|
|
|
@@ -8814,7 +8823,9 @@ ipcMain.handle('finance:settings', async (_e, patch) => {
|
|
|
8814
8823
|
return { ok: true, cfg: f };
|
|
8815
8824
|
});
|
|
8816
8825
|
|
|
8817
|
-
|
|
8826
|
+
/* Trader botu başlat: ANA trader varsa sürdür, yoksa yeni sürekli ajan aç.
|
|
8827
|
+
finance:trader:start'tan VE finance modu AÇILDIĞINDAN otomatik çağrılır. */
|
|
8828
|
+
async function financeTraderStart() {
|
|
8818
8829
|
if (!engine) return { ok: false, error: 'ajan hazır değil' };
|
|
8819
8830
|
const f = finCfg();
|
|
8820
8831
|
if (!engine.publicState().hasModel && !f.traderSel) return { ok: false, error: 'model yok — Ayarlar → Provider' };
|
|
@@ -8828,6 +8839,11 @@ ipcMain.handle('finance:trader:start', async () => {
|
|
|
8828
8839
|
break;
|
|
8829
8840
|
}
|
|
8830
8841
|
}
|
|
8842
|
+
/* zaten koşuyorsa yeniden brief düşürme — sessizce onayla */
|
|
8843
|
+
if (mainSid && financeState.traderOn) {
|
|
8844
|
+
const mj = engine._bgJobs && engine._bgJobs.get(mainSid);
|
|
8845
|
+
if (mj && mj.status === 'running') return { ok: true, sid: mainSid, already: true };
|
|
8846
|
+
}
|
|
8831
8847
|
if (!mainSid) {
|
|
8832
8848
|
const created = finAgentCreate([], true);
|
|
8833
8849
|
mainSid = String(created.s.id);
|
|
@@ -8845,7 +8861,9 @@ ipcMain.handle('finance:trader:start', async () => {
|
|
|
8845
8861
|
financeLog('[trader] başlatıldı (model: ' + (f.traderSel || 'genel aktif model') + ')');
|
|
8846
8862
|
finPush('trader', { state: 'running', round: mainAgent.round });
|
|
8847
8863
|
return { ok: true, sid: mainSid };
|
|
8848
|
-
}
|
|
8864
|
+
}
|
|
8865
|
+
|
|
8866
|
+
ipcMain.handle('finance:trader:start', () => financeTraderStart());
|
|
8849
8867
|
|
|
8850
8868
|
ipcMain.handle('finance:trader:stop', async () => {
|
|
8851
8869
|
financeState.traderOn = false;
|
package/src/renderer/renderer.js
CHANGED
|
@@ -5031,18 +5031,35 @@ async function loadRailChat(id) {
|
|
|
5031
5031
|
açılır — gelen/giden hepsi kronolojik. Thread = ajan çifti + KONU. */
|
|
5032
5032
|
|
|
5033
5033
|
const dmState = {
|
|
5034
|
-
dms: [], // { at, from, fromTitle, to, toTitle, topic, text }
|
|
5034
|
+
dms: [], // { at, from, fromTitle, to, toTitle, topic, text, group?, closed? }
|
|
5035
|
+
groups: [], // { id, title, members, titles, createdAt, closed, closedAt }
|
|
5035
5036
|
modalThread: null, // modalde açık oturum key'i (null = oturum listesi)
|
|
5036
5037
|
};
|
|
5037
5038
|
|
|
5039
|
+
function dmGroupOf(id) {
|
|
5040
|
+
return (dmState.groups || []).find((g) => String(g.id) === String(id)) || null;
|
|
5041
|
+
}
|
|
5042
|
+
|
|
5038
5043
|
function dmThreadOf(dm) {
|
|
5044
|
+
if (dm.group) return 'G|' + String(dm.group);
|
|
5039
5045
|
const a = String(dm.from || '');
|
|
5040
5046
|
const b = String(dm.to || '');
|
|
5041
5047
|
const pair = a < b ? a + '|' + b : b + '|' + a;
|
|
5042
5048
|
return pair + '|' + String(dm.topic || '(genel)').toLowerCase();
|
|
5043
5049
|
}
|
|
5044
5050
|
|
|
5051
|
+
function dmThreadClosed(msgs) {
|
|
5052
|
+
const last = msgs[msgs.length - 1];
|
|
5053
|
+
if (!last) return false;
|
|
5054
|
+
if (last.group) {
|
|
5055
|
+
const g = dmGroupOf(last.group);
|
|
5056
|
+
return !!(g && g.closed);
|
|
5057
|
+
}
|
|
5058
|
+
return !!last.closed;
|
|
5059
|
+
}
|
|
5060
|
+
|
|
5045
5061
|
function dmThreadTitle(dm) {
|
|
5062
|
+
if (dm.group) return '👥 ' + String(dm.groupTitle || 'Grup');
|
|
5046
5063
|
const a = String(dm.fromTitle || 'Ajan');
|
|
5047
5064
|
const b = String(dm.toTitle || 'Ajan');
|
|
5048
5065
|
const pair = a < b ? a + ' ↔ ' + b : b + ' ↔ ' + a;
|
|
@@ -5069,14 +5086,61 @@ function dmTime(iso) {
|
|
|
5069
5086
|
}
|
|
5070
5087
|
|
|
5071
5088
|
function dmMsgHtml(m) {
|
|
5089
|
+
const who = m.group
|
|
5090
|
+
? escapeHtml(String(m.fromTitle || 'Ajan'))
|
|
5091
|
+
: `${escapeHtml(String(m.fromTitle || 'Ajan'))} → ${escapeHtml(String(m.toTitle || 'Ajan'))}`;
|
|
5072
5092
|
return (
|
|
5073
|
-
`<div class="dmt-msg"><span class="dm-who">${
|
|
5093
|
+
`<div class="dmt-msg"><span class="dm-who">${who}</span>` +
|
|
5074
5094
|
`${escapeHtml(String(m.text || ''))}<span class="dm-at">${dmTime(m.at)}</span></div>`
|
|
5075
5095
|
);
|
|
5076
5096
|
}
|
|
5077
5097
|
|
|
5098
|
+
/* bir sohbet kartı (rail + modal ortak): AKTİF/GEÇMİŞ ayrımı için closed
|
|
5099
|
+
bayrağı kartta işaretlenir */
|
|
5100
|
+
function dmThreadCard(key, msgs, onClick) {
|
|
5101
|
+
const last = msgs[msgs.length - 1];
|
|
5102
|
+
const closed = dmThreadClosed(msgs);
|
|
5103
|
+
const el = document.createElement('div');
|
|
5104
|
+
el.className = 'dmt' + (closed ? ' dmt-closed' : '');
|
|
5105
|
+
let membersLine = '';
|
|
5106
|
+
if (last.group) {
|
|
5107
|
+
const g = dmGroupOf(last.group);
|
|
5108
|
+
if (g && Array.isArray(g.members) && g.members.length) {
|
|
5109
|
+
const names = g.members.map((m) => (g.titles && g.titles[m]) || m).slice(0, 4);
|
|
5110
|
+
membersLine = `<div class="dmt-members">👥 ${escapeHtml(names.join(', '))}</div>`;
|
|
5111
|
+
}
|
|
5112
|
+
}
|
|
5113
|
+
el.innerHTML =
|
|
5114
|
+
'<div class="dmt-head">' +
|
|
5115
|
+
`<span class="dmt-title">${escapeHtml(dmThreadTitle(last))}</span>` +
|
|
5116
|
+
`<span class="dmt-count">${closed ? 'kapandı' : msgs.length}</span>` +
|
|
5117
|
+
'</div>' +
|
|
5118
|
+
membersLine +
|
|
5119
|
+
`<div class="dmt-last">${escapeHtml(String(last.text || '').replace(/\s+/g, ' ').slice(0, 90))} · ${dmTime(last.at)}</div>`;
|
|
5120
|
+
el.addEventListener('click', onClick);
|
|
5121
|
+
return el;
|
|
5122
|
+
}
|
|
5123
|
+
|
|
5078
5124
|
/* --- sağ sütun: OTURUM LİSTESİ (rail gibi) --- */
|
|
5079
5125
|
|
|
5126
|
+
/* AKTİF/GEÇMİŞ ayrılmış sohbet listesi (rail + modal ortak) */
|
|
5127
|
+
function dmAppendThreadSections(list, onClickThread) {
|
|
5128
|
+
const all = dmThreads();
|
|
5129
|
+
const act = all.filter(([, msgs]) => !dmThreadClosed(msgs));
|
|
5130
|
+
const past = all.filter(([, msgs]) => dmThreadClosed(msgs));
|
|
5131
|
+
const sec = (label, items) => {
|
|
5132
|
+
if (!items.length) return;
|
|
5133
|
+
const h = document.createElement('div');
|
|
5134
|
+
h.className = 'dm-sec';
|
|
5135
|
+
h.textContent = label;
|
|
5136
|
+
list.appendChild(h);
|
|
5137
|
+
for (const [key, msgs] of items) list.appendChild(dmThreadCard(key, msgs, () => onClickThread(key)));
|
|
5138
|
+
};
|
|
5139
|
+
sec('● AKTİF', act);
|
|
5140
|
+
sec('— GEÇMİŞ', past);
|
|
5141
|
+
return all.length;
|
|
5142
|
+
}
|
|
5143
|
+
|
|
5080
5144
|
function renderDmRail() {
|
|
5081
5145
|
const list = els.dmRailList;
|
|
5082
5146
|
if (!list) return;
|
|
@@ -5085,25 +5149,17 @@ function renderDmRail() {
|
|
|
5085
5149
|
return;
|
|
5086
5150
|
}
|
|
5087
5151
|
list.innerHTML = '';
|
|
5088
|
-
|
|
5089
|
-
const last = msgs[msgs.length - 1];
|
|
5090
|
-
const el = document.createElement('div');
|
|
5091
|
-
el.className = 'dmt';
|
|
5092
|
-
el.innerHTML =
|
|
5093
|
-
'<div class="dmt-head">' +
|
|
5094
|
-
`<span class="dmt-title">${escapeHtml(dmThreadTitle(last))}</span>` +
|
|
5095
|
-
`<span class="dmt-count">${msgs.length}</span>` +
|
|
5096
|
-
'</div>' +
|
|
5097
|
-
`<div class="dmt-last">${escapeHtml(String(last.text || '').replace(/\s+/g, ' ').slice(0, 90))} · ${dmTime(last.at)}</div>`;
|
|
5098
|
-
el.addEventListener('click', () => openDmModal(key)); // sohbet yalnız modalde
|
|
5099
|
-
list.appendChild(el);
|
|
5100
|
-
}
|
|
5152
|
+
dmAppendThreadSections(list, (key) => openDmModal(key)); // sohbet yalnız modalde
|
|
5101
5153
|
}
|
|
5102
5154
|
|
|
5103
5155
|
async function refreshDmRail() {
|
|
5104
5156
|
try {
|
|
5105
|
-
const
|
|
5106
|
-
if (Array.isArray(
|
|
5157
|
+
const res = await beast.agentDmsList();
|
|
5158
|
+
if (Array.isArray(res)) dmState.dms = res;
|
|
5159
|
+
else {
|
|
5160
|
+
dmState.dms = (res && res.dms) || [];
|
|
5161
|
+
dmState.groups = (res && res.groups) || [];
|
|
5162
|
+
}
|
|
5107
5163
|
} catch {}
|
|
5108
5164
|
renderDmRail();
|
|
5109
5165
|
if (els.dmOverlay && !els.dmOverlay.hidden) renderDmModal();
|
|
@@ -5142,26 +5198,14 @@ function renderDmModal() {
|
|
|
5142
5198
|
if (box) box.scrollTop = box.scrollHeight;
|
|
5143
5199
|
return;
|
|
5144
5200
|
}
|
|
5145
|
-
/* OTURUM LİSTESİ */
|
|
5146
|
-
if (els.dmDialogTitle) els.dmDialogTitle.textContent = 'AJAN DM —
|
|
5201
|
+
/* OTURUM LİSTESİ — AKTİF + GEÇMİŞ bölümleri */
|
|
5202
|
+
if (els.dmDialogTitle) els.dmDialogTitle.textContent = 'AJAN DM — AKTİF & GEÇMİŞ';
|
|
5147
5203
|
if (els.dmBackBtn) els.dmBackBtn.hidden = true;
|
|
5148
5204
|
list.innerHTML = '';
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
el.innerHTML =
|
|
5154
|
-
'<div class="dmt-head">' +
|
|
5155
|
-
`<span class="dmt-title">${escapeHtml(dmThreadTitle(last))}</span>` +
|
|
5156
|
-
`<span class="dmt-count">${msgs.length}</span>` +
|
|
5157
|
-
'</div>' +
|
|
5158
|
-
`<div class="dmt-last">${escapeHtml(String(last.text || '').replace(/\s+/g, ' ').slice(0, 120))} · ${dmTime(last.at)}</div>`;
|
|
5159
|
-
el.addEventListener('click', () => {
|
|
5160
|
-
dmState.modalThread = key;
|
|
5161
|
-
renderDmModal();
|
|
5162
|
-
});
|
|
5163
|
-
list.appendChild(el);
|
|
5164
|
-
}
|
|
5205
|
+
dmAppendThreadSections(list, (key) => {
|
|
5206
|
+
dmState.modalThread = key;
|
|
5207
|
+
renderDmModal();
|
|
5208
|
+
});
|
|
5165
5209
|
}
|
|
5166
5210
|
|
|
5167
5211
|
function openDmModal(threadKey) {
|
package/src/renderer/style.css
CHANGED
|
@@ -2157,6 +2157,18 @@ body.rail-hidden #dmRailHead { padding-right: 148px; }
|
|
|
2157
2157
|
font-size: 11px; margin-top: 4px;
|
|
2158
2158
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
2159
2159
|
}
|
|
2160
|
+
/* AJAN DM: AKTİF/GEÇMİŞ bölüm başlıkları + kapalı sohbet görünümü */
|
|
2161
|
+
.dm-sec {
|
|
2162
|
+
font-size: 10.5px; font-weight: 800; letter-spacing: 0.08em;
|
|
2163
|
+
color: var(--muted);
|
|
2164
|
+
padding: 8px 2px 4px;
|
|
2165
|
+
}
|
|
2166
|
+
.dmt.dmt-closed { opacity: 0.62; }
|
|
2167
|
+
.dmt.dmt-closed .dmt-count { background: var(--border); color: var(--muted); }
|
|
2168
|
+
.dmt-members {
|
|
2169
|
+
font-size: 10.5px; color: var(--muted); margin-top: 3px;
|
|
2170
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
2171
|
+
}
|
|
2160
2172
|
.dmt-msgs {
|
|
2161
2173
|
display: none;
|
|
2162
2174
|
margin-top: 6px;
|