beast-agent 2.22.1 → 2.23.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "beast-agent",
3
3
  "productName": "Beast Agent",
4
- "version": "2.22.1",
4
+ "version": "2.23.0",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
@@ -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' +
@@ -3181,6 +3189,8 @@ class Engine {
3181
3189
  }
3182
3190
  /* #17 öz-kurtarma hakları tükendi → owner'a anlık altyapı uyarısı */
3183
3191
  this._notifyOwnerTaskFailed(job, msg);
3192
+ /* AJAN DM: işi kesin bitti — ajanın DM/grup sohbetleri kapanır */
3193
+ try { this._agentDmClose(sid); } catch {}
3184
3194
  if (grouped) {
3185
3195
  /* #18 hatalı üye de gruba yazılır — birleşik raporda görünecek */
3186
3196
  this._bgGroupRecord(job.title, 'error', 'HATA: ' + msg.slice(0, 300));
@@ -3204,6 +3214,11 @@ class Engine {
3204
3214
  this._pendingReports.push({ parentId: job.parentId, text });
3205
3215
  this.flushPendingReports(job.parentId);
3206
3216
  }
3217
+ /* AJAN DM: işi bitti (kullanıcı iptali dahil) — sohbetler kapanır */
3218
+ try { this._agentDmClose(sid); } catch {}
3219
+ } else if (status === 'done') {
3220
+ /* AJAN DM: görev tamamlandı — ajanın DM/grup sohbetleri kapanır */
3221
+ try { this._agentDmClose(sid); } catch {}
3207
3222
  }
3208
3223
  }
3209
3224
 
@@ -3464,7 +3479,7 @@ class Engine {
3464
3479
  `- Bağımsız araç çağrılarını aynı turda PARALEL ver.\n` +
3465
3480
  `- 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
3481
  `- 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` +
3482
+ `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
3483
  FORMAT_RULES + '\n' +
3469
3484
  `SON ÇIKTI: 3-5 satırlık net sonuç raporu, madde madde. Soru sorma, sohbet etme.`
3470
3485
  );
@@ -3551,7 +3566,7 @@ class Engine {
3551
3566
  ? this.buildStudioSystem(session)
3552
3567
  : bcAgent
3553
3568
  ? this.buildOcSystem(session, bcAgent)
3554
- : await this.buildSystem(promptText, session);
3569
+ : await this.buildSystem(promptText, session);
3555
3570
  // Granül izin: oturumun yetki seviyesine göre araç seti daraltılır.
3556
3571
  // Çoklu izin (ör. web+read) seçiliyse kümeler BİRLEŞİR — hepsinin araçları açık olur.
3557
3572
  // BC'de opencode adları Beast adına çevrilerek denetlenir (read → read_file).
@@ -4061,7 +4076,7 @@ class Engine {
4061
4076
  }
4062
4077
  }
4063
4078
  } catch {}
4064
- }
4079
+ }
4065
4080
  // Ekran görüntüsü gibi araçlar görseli sonraki tura enjekte eder
4066
4081
  let injectedImage = null;
4067
4082
  /* opencode ctx.metadata portu: edit/write sonucundaki diffView, LLM
@@ -5377,18 +5392,19 @@ const AGENT_DM_DEF = {
5377
5392
  function: {
5378
5393
  name: 'agent_dm',
5379
5394
  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.',
5395
+ '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
5396
  parameters: {
5382
5397
  type: 'object',
5383
5398
  properties: {
5384
5399
  to: { type: 'string', description: 'Target agent session id or a title keyword' },
5385
5400
  topic: { type: 'string', description: 'Short subject label for the conversation thread, e.g. "GOLD pozisyon riski"' },
5386
5401
  message: { type: 'string', description: 'Short message (1-3 sentences)' },
5402
+ 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
5403
  },
5388
- required: ['to', 'message'],
5404
+ required: ['message'],
5389
5405
  },
5390
5406
  },
5391
- };
5407
+ };
5392
5408
 
5393
5409
  const TOOLS = [
5394
5410
  ...tools.definitions,
@@ -6180,85 +6196,210 @@ Engine.prototype._agentDmSend = function (fromSid, args) {
6180
6196
  const to = String((args && args.to) || '').trim();
6181
6197
  const text = String((args && args.message) || '').trim().slice(0, 1200);
6182
6198
  if (!text) return { ok: false, error: 'mesaj boş' };
6199
+ const groupName = String((args && args.group) || '').replace(/\s+/g, ' ').trim().slice(0, 40);
6183
6200
  const jobs = this._bgJobs || new Map();
6184
- let target = jobs.has(to) ? to : '';
6185
- if (!target) {
6186
- const q = to.toLowerCase();
6187
- for (const [sid, j] of jobs) {
6188
- if (sid === String(fromSid)) continue;
6189
- if (j && j.title && String(j.title).toLowerCase().includes(q)) {
6190
- target = sid;
6191
- break;
6201
+ const fromJob = jobs.get(String(fromSid));
6202
+ /* gönderici koşan ajan değilse (düz chat oturumu) cache'ten oturum bilgisi */
6203
+ const fromSess = fromJob ? null : this.cache.get(String(fromSid)) || null;
6204
+ const fromTitle =
6205
+ fromJob && fromJob.title
6206
+ ? String(fromJob.title)
6207
+ : fromSess
6208
+ ? 'Beast · ' + String(fromSess.bgTitle || fromSess.title || fromSess.code || '')
6209
+ : 'Ajan';
6210
+ /* hedef çöz: oturum id YA DA başlık anahtar kelimesi (koşan ajanlar +
6211
+ ANA OTURUM FALLBACK: chat oturumları this.cache'te yaşar — finance
6212
+ trader → ana Beast DM zinciri böyle kapanır). */
6213
+ let target = '';
6214
+ if (to) {
6215
+ target = jobs.has(to) ? to : '';
6216
+ if (!target) {
6217
+ const q = to.toLowerCase();
6218
+ for (const [sid, j] of jobs) {
6219
+ if (sid === String(fromSid)) continue;
6220
+ if (j && j.title && String(j.title).toLowerCase().includes(q)) {
6221
+ target = sid;
6222
+ break;
6223
+ }
6192
6224
  }
6193
6225
  }
6194
- }
6195
- if (!target || !jobs.has(target)) {
6196
- const titles = [...jobs.values()]
6197
- .filter((j) => j && j.status === 'running' && j.title)
6198
- .map((j) => j.title)
6199
- .slice(0, 8);
6200
- return {
6201
- ok: false,
6202
- error: 'hedef ajan bulunamadı: "' + to + '"' + (titles.length ? ' — koşan ajanlar: ' + titles.join(', ') : ''),
6203
- };
6204
- }
6205
- if (String(target) === String(fromSid)) return { ok: false, error: 'kendine DM atılamaz' };
6206
- const fromJob = jobs.get(String(fromSid));
6207
- const toJob = jobs.get(target);
6208
- /* FİNANS SINIRI: Beast Finance oturumu agent_dm kullansa bile finance
6209
- dünyasının DIŞINA ÇIKMAZ — yalnız finance ajanlarına DM atabilir
6210
- (trader ↔ GOLD işçisi ↔ finance işçileri). Finance dışı hedef reddedilir. */
6211
- const fromSess = this.cache.get(String(fromSid)) || null;
6212
- if (fromSess && fromSess.finance) {
6213
- const targetSess = this.cache.get(String(target)) || null;
6214
- if (!targetSess || !targetSess.finance) {
6226
+ if (!target) {
6227
+ const q = to.toLowerCase();
6228
+ for (const [sid, s] of this.cache) {
6229
+ if (sid === String(fromSid)) continue;
6230
+ if (!s || s.bgJob || s.isBotDm) continue;
6231
+ if (s.botId && s.botId !== 'beast') continue;
6232
+ const hay = [s.bgTitle, s.title, s.code].map((x) => String(x || '').toLowerCase());
6233
+ if (hay.some((x) => x && x.includes(q))) {
6234
+ target = sid;
6235
+ break;
6236
+ }
6237
+ }
6238
+ }
6239
+ if (String(target) === String(fromSid)) return { ok: false, error: 'kendine DM atılamaz' };
6240
+ if (!target) {
6241
+ const titles = [...jobs.values()]
6242
+ .filter((j) => j && j.status === 'running' && j.title)
6243
+ .map((j) => j.title)
6244
+ .slice(0, 8);
6215
6245
  return {
6216
6246
  ok: false,
6217
- error:
6218
- 'Beast Finance ajanı yalnız finance dünyasındaki ajanlara DM atabilir — hedef finance dışı: ' +
6219
- String((toJob && toJob.title) || to),
6247
+ error: 'hedef ajan bulunamadı: "' + to + '"' + (titles.length ? ' — koşan ajanlar: ' + titles.join(', ') : ''),
6220
6248
  };
6221
6249
  }
6222
- }
6250
+ } else if (!groupName) {
6251
+ return { ok: false, error: 'to (hedef ajan) ya da group (grup adı) gerekli' };
6252
+ }
6253
+ const toJob = target ? jobs.get(target) : null;
6254
+ const mainChatSess = target && !toJob ? this.cache.get(String(target)) || null : null;
6255
+ const toTitle = target
6256
+ ? toJob
6257
+ ? String((toJob && toJob.title) || 'Ajan')
6258
+ : String((mainChatSess && (mainChatSess.bgTitle || mainChatSess.title)) || 'Beast')
6259
+ : '';
6223
6260
  /* konu etiketi: aynı konudaki DM'ler panelde AYNI oturumda toplanır */
6224
6261
  const topic = String((args && args.topic) || '').replace(/\s+/g, ' ').trim().slice(0, 60) || '(genel)';
6225
- const dm = {
6226
- at: new Date().toISOString(),
6227
- from: String(fromSid),
6228
- fromTitle: (fromJob && fromJob.title) || 'Ajan',
6229
- to: String(target),
6230
- toTitle: (toJob && toJob.title) || 'Ajan',
6231
- topic,
6232
- text,
6233
- };
6234
- this._agentDms = this._agentDms || [];
6235
- this._agentDms.push(dm);
6236
- if (this._agentDms.length > 400) this._agentDms.splice(0, this._agentDms.length - 400);
6237
- try {
6238
- fs.writeFileSync(this._agentDmsFile, JSON.stringify({ dms: this._agentDms }, null, 2));
6239
- } catch {}
6262
+ const at = new Date().toISOString();
6263
+
6264
+ /* GRUP SOHBETİ: kur/yeniden kullan, hedefi üye yap, mesaj HER ÜYEYE düşer.
6265
+ Ajanlar ortak kararları burada tartışır; iş bitince sohbet kapanır. */
6266
+ if (groupName) {
6267
+ /* gid Türkçe-kararsız normalize: ALTIN/Altın/altın aynı grup */
6268
+ const gid =
6269
+ 'grp:' +
6270
+ groupName
6271
+ .toLowerCase()
6272
+ .replace(/\u0307/g, '')
6273
+ .replace(/ı/g, 'i')
6274
+ .replace(/ğ/g, 'g')
6275
+ .replace(/ü/g, 'u')
6276
+ .replace(/ş/g, 's')
6277
+ .replace(/ö/g, 'o')
6278
+ .replace(/ç/g, 'c');
6279
+ let g = this._agentGroups.get(gid);
6280
+ if (!g) {
6281
+ if (!target) return { ok: false, error: 'yeni grup için to ile en az bir ajan belirt' };
6282
+ g = { id: gid, title: groupName, members: [], titles: {}, createdAt: at, closed: false };
6283
+ this._agentGroups.set(gid, g);
6284
+ }
6285
+ g.titles = g.titles || {};
6286
+ const addMember = (sid, title) => {
6287
+ sid = String(sid);
6288
+ if (!g.members.includes(sid)) g.members.push(sid);
6289
+ if (title) g.titles[sid] = title;
6290
+ };
6291
+ addMember(fromSid, fromTitle);
6292
+ if (target) addMember(target, toTitle);
6293
+ g.closed = false; /* yeni mesaj → sohbet yeniden aktif */
6294
+ delete g.closedAt;
6295
+ const dm = {
6296
+ at,
6297
+ from: String(fromSid),
6298
+ fromTitle,
6299
+ to: target || '',
6300
+ toTitle: target ? toTitle : 'grup',
6301
+ group: gid,
6302
+ groupTitle: g.title,
6303
+ topic,
6304
+ text,
6305
+ };
6306
+ this._pushAgentDm(dm);
6307
+ for (const m of g.members) {
6308
+ if (m === String(fromSid) || !jobs.has(m)) continue;
6309
+ emitSafe(this, m, { type: 'agent-dm', ...dm });
6310
+ (this._pendingReports = this._pendingReports || []).push({
6311
+ parentId: m,
6312
+ text:
6313
+ `[AJAN DM (grup: "${g.title}") — ${dm.fromTitle} · konu: "${topic}"]\n${text}\n` +
6314
+ `(Cevabını agent_dm aracıyla ver — group: "${g.title}", to: "${dm.fromTitle}", topic: "${topic}")`,
6315
+ });
6316
+ this.flushPendingReports(m);
6317
+ }
6318
+ this._persistAgentDms();
6319
+ return {
6320
+ ok: true,
6321
+ group: g.title,
6322
+ members: g.members.map((m) => g.titles[m] || m),
6323
+ ...(target ? { to: target, toTitle } : {}),
6324
+ topic,
6325
+ };
6326
+ }
6327
+
6328
+ /* BİREYSEL DM — DÜZ CHAT OTURUMUNA (sahibin sohbet geçmişi) ENJEKTE
6329
+ ETMEYİZ: DM trafik yalnız AJAN DM konsolunda görünür. */
6330
+ const dm = { at, from: String(fromSid), fromTitle, to: String(target), toTitle, topic, text };
6331
+ this._pushAgentDm(dm);
6332
+ this._persistAgentDms();
6240
6333
  emitSafe(this, target, { type: 'agent-dm', ...dm });
6241
- /* teslim: hedef meşgulse pending kuyruğunda bekler, done olunca düşer.
6242
- Cevap zinciri AYNI thread'den sürsün diye topic geri bildirilir. */
6243
- (this._pendingReports = this._pendingReports || []).push({
6244
- parentId: target,
6245
- text:
6246
- `[AJAN DM — ${dm.fromTitle} · konu: "${topic}"]\n${text}\n` +
6247
- `(Cevabını agent_dm aracıyla ver — to: "${dm.fromTitle}", topic: "${topic}")`,
6248
- });
6249
- this.flushPendingReports(target);
6334
+ if (jobs.has(target)) {
6335
+ (this._pendingReports = this._pendingReports || []).push({
6336
+ parentId: target,
6337
+ text:
6338
+ `[AJAN DM — ${dm.fromTitle} · konu: "${topic}"]\n${text}\n` +
6339
+ `(Cevabını agent_dm aracıyla ver — to: "${dm.fromTitle}", topic: "${topic}")`,
6340
+ });
6341
+ this.flushPendingReports(target);
6342
+ }
6250
6343
  return { ok: true, to: target, toTitle: dm.toTitle, topic };
6251
6344
  } catch (e) {
6252
6345
  return { ok: false, error: String((e && e.message) || e) };
6253
6346
  }
6254
6347
  };
6255
6348
 
6349
+ Engine.prototype._pushAgentDm = function (dm) {
6350
+ this._agentDms = this._agentDms || [];
6351
+ this._agentDms.push(dm);
6352
+ if (this._agentDms.length > 600) this._agentDms.splice(0, this._agentDms.length - 600);
6353
+ };
6354
+
6355
+ Engine.prototype._persistAgentDms = function () {
6356
+ try {
6357
+ const groups = {};
6358
+ for (const [, g] of this._agentGroups || new Map()) groups[g.id] = g;
6359
+ fs.writeFileSync(this._agentDmsFile, JSON.stringify({ dms: this._agentDms, groups }, null, 2));
6360
+ } catch {}
6361
+ };
6362
+
6363
+ /* AJAN İŞİ BİTİNCE: o ajanın bireysel DM'leri kapanır; üyesi olduğu
6364
+ gruplarda DİĞER koşan ajan kalmadıysa grup sohbeti de kapanır.
6365
+ Geçmiş panelde GÖRÜNMEYE DEVAM EDER (silinmez, "Geçmiş" bölümüne düşer). */
6366
+ Engine.prototype._agentDmClose = function (sid) {
6367
+ sid = String(sid);
6368
+ const now = new Date().toISOString();
6369
+ let changed = false;
6370
+ for (const dm of this._agentDms || []) {
6371
+ if (dm.closed || dm.group) continue;
6372
+ if (String(dm.from) === sid || String(dm.to) === sid) {
6373
+ dm.closed = true;
6374
+ dm.closedAt = now;
6375
+ changed = true;
6376
+ }
6377
+ }
6378
+ const jobs = this._bgJobs || new Map();
6379
+ for (const [, g] of this._agentGroups || new Map()) {
6380
+ if (g.closed || !(g.members || []).includes(sid)) continue;
6381
+ const anyRunning = g.members.some((m) => {
6382
+ const j = jobs.get(m);
6383
+ return j && j.status === 'running';
6384
+ });
6385
+ if (!anyRunning) {
6386
+ g.closed = true;
6387
+ g.closedAt = now;
6388
+ changed = true;
6389
+ }
6390
+ }
6391
+ if (changed) this._persistAgentDms();
6392
+ };
6393
+
6256
6394
  Engine.prototype.agentDmsList = function () {
6257
- return [...(this._agentDms || [])].slice(-200);
6395
+ const groups = [];
6396
+ for (const [, g] of this._agentGroups || new Map()) groups.push(g);
6397
+ return { dms: [...(this._agentDms || [])].slice(-600), groups };
6258
6398
  };
6259
6399
 
6260
6400
  Engine.prototype.agentDmsClear = function () {
6261
6401
  this._agentDms = [];
6402
+ this._agentGroups = new Map();
6262
6403
  try { fs.rmSync(this._agentDmsFile, { force: true }); } catch {}
6263
6404
  return { ok: true };
6264
6405
  };
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
- ipcMain.handle('finance:trader:start', async () => {
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;
@@ -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">${escapeHtml(String(m.fromTitle || 'Ajan'))} → ${escapeHtml(String(m.toTitle || 'Ajan'))}</span>` +
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
- for (const [key, msgs] of dmThreads()) {
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 dms = await beast.agentDmsList();
5106
- if (Array.isArray(dms)) dmState.dms = dms;
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 — OTURUMLAR';
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
- for (const [key, msgs] of dmThreads()) {
5150
- const last = msgs[msgs.length - 1];
5151
- const el = document.createElement('div');
5152
- el.className = 'dmt';
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) {
@@ -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;